DbContext.Set выдает нулевое исключение в тестах xUnit

#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> создает a DbSet<TEntity> , который можно использовать для запроса и сохранения экземпляров TEntity. В моем случае TEntity всегда возвращается значение null, хотя я все равно передаю его в класс public class StudentUnitOfWork : MyGenericRepository<Student> . Student вот мое TEntity , но когда я запускаю метод Add, я ожидаю context.Students null , что вместо этого я получаю. Надеюсь, это объяснение поможет.

4. @Nana нет, вы неправильно понимаете. Set<T>() Метод не имеет никакого отношения к DbSet<T> свойствам. Да, оба должны возвращать одно и то же, а именно a DbSet<T> для данного объекта, но при издевательстве вам нужно настроить тот, к которому вы собираетесь получить доступ. Вызов Setup() одного не приводит к волшебной настройке другого.