#c# #entity-framework #unit-testing #.net-core #xunit
#c# #entity-framework #модульное тестирование #.net-ядро #xunit
Вопрос:
Я реализовал абстрактный универсальный репозиторий, который определяет набор методов для выполнения операций CRUD. Я пытаюсь протестировать их с помощью шаблона Unit of Work в производном классе. Я использую Moq в качестве среды тестирования и MockQueryable для включения асинхронных операций, таких как FirstOrDefaultAsync, addAsync, SaveChangesAsync и т.д. Я испытываю ошибку при тестировании метода Add. Вот фиктивный проект, который я создал, чтобы показать, как все настроено.
Класс DbContext
public class MyDBContextClass : DbContext, IDbContext
{
public MyDBContextClass(DbContextOptions options) : base(options) { }
public DbSet<Students> Students { get; set; }
}
Абстрактный класс
public abstract class MyGenericRepository<TEntity> where TEntity : class
{
private readonly IDbContext context;
public MyMyGenericRepository(IDbContext context){
this.context = context;
}
public virtual async Task<int> Add(TEntity Entity)
{
await context.Set<TEntity>().AddAsync(entity); // throws a NullReferenceException during test
await context.SaveChangesAsync();
}
}
Единица рабочего класса
public class StudentUnitOfWork : MyGenericRepository<Student>
{
public class StudentUnitOfWork(IDBContext context) : base(context)
{
}
}
Тестовый класс
public class StudentUnitOfWorkTest
{
[Fact]
public async Task Add()
{
var students = new List<Student>
{
new Student{ Id = 1, Name = "John Doe"},
new Student{ Id = 2, Name = "Jane Doe"}
}
var mockSet = students.AsQueryable().BuildMockDbSet();
var mockContext = new Mock<IDbContext>();
mockContext.Setup(_ => _.Students).Returns(mockSet.Object);
var sut = new StudentUnitOfWork(mockContext.Object);
var newStudentObj = new Student
{
Name = "Justin Doe"
}
await sut.Add(newStudentObj); // throws a NullReferenceException
mockSet.Verify(_ => _.AddAsync(It.IsAny<Student>(), It.IsAny<CancellationToken>()), Times.Once());
mockContext.Verify(_ => _.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once());
}
}
Переопределение и внедрение базового метода Add также завершается с ошибкой
public override async Task Add(Student student)
{
await base.Add(category); // Still throws a NullReferenceException
}
Тест проходит, когда я переопределяю реализацию Add в классе Unit of Work и устанавливаю фактический DbSet в данном случае Students .
public class StudentUnitOfWork : MyGenericRepository<Student>
{
public override async Task Add(Student student)
{
await context.Students.AddAsync(student); // Works
await context.SaveChangesAsync();
}
}
Я что-то пропустил или DbContext .Set работает только в рабочей среде, а не в тестах.
Комментарии:
1.
DbSet<Students>
иMyGenericRepository<Student>
— это опечатка или разные типы?2. @GuruStron Это опечатка.
Ответ №1:
Вы не настраиваете метод Set в IDbContext mock для возврата чего-либо в вашем коде настройки, поэтому он возвращает null (по умолчанию), и при вызове addAsync генерируется исключение.
Обновить:
Вот строка с ошибкой, в которой вы вызываете метод Set:
await context.Set<TEntity>().AddAsync(entity);
И вот ваш установочный код в test, где вы пропускаете обработку того же метода Set:
var mockSet = students.AsQueryable().BuildMockDbSet();
var mockContext = new Mock<IDbContext>();
mockContext.Setup(_ => _.Students).Returns(mockSet.Object);
И поскольку вы не издеваетесь над методом Set, он возвращает null при выполнении теста. Очевидно, что вы не можете вызвать метод addAsync для null, поэтому возникает исключение NullReferenceException .
Вы должны решить это, добавив что-то вроде этого:
mockContext.Setup(_ => _.Set<Student>()).Returns(<DbSet mock object>);
С другой стороны,
await context.Students.AddAsync(student);
работает, потому что вы издевались над свойством Students
Комментарии:
1. В тесте у меня есть строка, которая отвечает на ваш вопрос
mockContext.Setup(_ => _.Students).Returns(mockSet.Object);
2. Опять же, я говорю о методе Set, а не о свойстве Students.
3. Вы, кажется, сбиты с толку тем, что делает метод Set.
DbContext.Set<TEntity>
создает aDbSet<TEntity>
, который можно использовать для запроса и сохранения экземпляров TEntity. В моем случаеTEntity
всегда возвращается значение null, хотя я все равно передаю его в классpublic class StudentUnitOfWork : MyGenericRepository<Student>
.Student
вот моеTEntity
, но когда я запускаю метод Add, я ожидаюcontext.Students
null
, что вместо этого я получаю. Надеюсь, это объяснение поможет.4. @Nana нет, вы неправильно понимаете.
Set<T>()
Метод не имеет никакого отношения кDbSet<T>
свойствам. Да, оба должны возвращать одно и то же, а именно aDbSet<T>
для данного объекта, но при издевательстве вам нужно настроить тот, к которому вы собираетесь получить доступ. ВызовSetup()
одного не приводит к волшебной настройке другого.