0

我已经实现了一个抽象的通用存储库,它定义了一组用于执行 CRUD 操作的方法。我正在尝试借助派生类中的工作单元模式来测试它们。我使用 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 的实现并在这种情况下设置实际的 dbSet 时,测试通过了 Student。

public class StudentUnitOfWork : MyGenericRepository<Student>
{
   public override async Task Add(Student student)
   {
       await context.Students.AddAsync(student); // Works
       await context.SaveChangesAsync();
   }
} 

我是否遗漏了任何东西或 DbContext.Set 仅适用于生产而不是测试。

4

1 回答 1

1

您没有在 IDbContext 模拟中配置 Set 方法以在设置代码中返回任何内容,因此它返回 null(默认)并且在调用 AddAsync 时引发异常。

更新:

这是有错误的行,您在其中调用 Set 方法:

await context.Set<TEntity>().AddAsync(entity);

这是您在测试中的设置代码,您可以在其中跳过处理相同的 Set 方法:

var mockSet = students.AsQueryable().BuildMockDbSet();
var mockContext = new Mock<IDbContext>();
mockContext.Setup(_ => _.Students).Returns(mockSet.Object);

而且由于您没有模拟 Set 方法,因此它在测试运行时返回 null 。您显然不能在 null 上调用 AddAsync 方法,因此会引发 NullReferenceException。

你应该通过添加类似的东西来解决它:

mockContext.Setup(_ => _.Set<Student>()).Returns(<DbSet mock object>);

另一方面,

await context.Students.AddAsync(student);

有效,因为您嘲笑了学生的财产

于 2021-01-30T20:52:26.873 回答