使用 NSubstitute 测试实体框架的存储库查询

问题描述 投票:0回答:1

我正在尝试模拟我的 dbContext 以测试我的存储库查询/过滤器。我现在已经尝试使用内存数据库或模拟数据库上下文。在这两种情况下,我只能在不使用异步调用时才能使其工作。另外,对于 MockQueryable.NSubstitute,在运行 FirstOrDefaultAsync 之前有额外的过滤条件时,我无法让它工作。

我总是例外:“IQueryable”没有实现“IAsyncQueryProvider”

我不知道在哪里以及如何实施该提供程序。还有好的例子吗?

测试设置:

[Fact]
public async Task Get_ById_Returns_CorrectRequirement()
{
    // Arrange
    long requirementId = 7;
    string ntUser = "anyUser";

    var options = new DbContextOptionsBuilder<MyDbContext>()
        .UseInMemoryDatabase(databaseName: "TestDatabase")
        .Options;

    using var context = new MyDbContext(options);

    context.TDummyTable.AddRange(new List<TDummyTable>
{
    new TDummyTable { PkKey = 1, Valid = true },
    new TDummyTable { PkKey = requirementId, Valid = true },
});
    await context.SaveChangesAsync();

    // Act
    var requirement = await _sut.Get(requirementId, ntUser);

    // Assert
    Assert.Equal(requirementId, requirement.PkKey);
}

回购方法:

public async Task<TDummyTable> Get(long id, string userNt)
{
    var itemsQuery = this.context.TDummyTable.AsQueryable();
    itemsQuery = itemsQuery.Where(x => x.PkKey == 23);
    return await itemsQuery.FirstOrDefaultAsync(x => x.PkKey.Equals(id));
}

编辑: 这就是我尝试在排列中使用 MockQueryable 的方式

    var data = new List<TDummyTable>
    {
        new TDummyTable { PkKey = 1, Valid = true},
        new TDummyTable { PkKey = requirementId,  Valid = true},
    };


    var mock = data.AsQueryable().BuildMockDbSet();
    context.TDummyTable.Returns(mock);
unit-testing entity-framework-core dbcontext iqueryable nsubstitute
1个回答
0
投票

不幸的是,我没有 NSubstitute 的经验,因为我使用 Moq 进行模拟,但如果您使用存储库模式,那么想法通常是让存储库作为模拟的边界,避免需要深入到

DbContext
/
DbSet

存储库是域数据的看门人。业务逻辑存在于控制器、服务等中,这就是您想要测试的内容。因此,当您的业务逻辑将转到存储库的 .Get() 方法时,您可以模拟存储库,而不是使用模拟的 DbContext 调用具体存储库

var mockRepo = new Mock<DummyTableRepository>();
mockRepo.Setup(x => x.GetAsync(expectedId, expectedUserNt))
   .ReturnsAsync( new TDummyTable { PkKey = expectedId, Valid = true });

上面是 Moq,但是 NSubstitute 应该有类似的功能来处理模拟异步调用。

当前方法的问题是您正在尝试测试 Linq,

Where
条件可以从集合中过滤行。 Linq 已经测试过,EF 也已经测试过。现在,您可能在存储库方法中强制执行一些更复杂的条件,但我建议将这些条件断言为集成测试(针对实时数据库的测试)的一部分,这些测试作为持续集成的一部分运行,而不是写下单元测试在数据库级别,甚至使用内存数据库之类的东西。原因是内存中的 Linq 和给定数据库的 SQL 之间的行为可能会发生变化,而且大多数情况下单元测试应该关注业务逻辑,即本质上可变的东西。

MockQueryable 发挥作用的地方:

有时您的存储库不会返回单个实体,而是返回实体集。一种选择是简单地让存储库返回

IEnumerable<TEntity>
IList<TEntity>
,但这不是很有效。您可能希望使用投影来获取一组 ViewModel、分页、应用排序等。此外还可以公开同步和异步风格。将所有这些问题传递到存储库非常复杂,并且会导致大量的样板文件。所以一种选择是允许存储库返回
IQueryable<TEntity>
。通过这种方式,存储库的调用者可以准确决定如何使用结果。模拟存储库不能简单地传回
new [] { new TEntity {...}, new TEntity {...}, ... }.AsQueryable();
if 消费者可能正在对其使用异步调用,因此您可以使用 MockQueryable 来返回:

var query = new [] 
{ 
    new TEntity {...}, 
    new TEntity {...}, 
    ... 
}.BuildMock();

这样,存储库的使用者就可以对结果使用同步或异步调用。

MockQueryable 有一个

BuildMockDbSet()
,适用于被测试的服务使用注入的
DbContext
而不是存储库的情况。您将创建
DbContext
的模拟,然后初始化它对
DbSet
实例的
BuildMockDbSet()
引用。看起来这可能是您在注入存储库的 DbContext 中尝试的操作,但看起来这个示例并不是完整的图片,可以看出为什么它不起作用。由于您有一个存储库,我强烈建议避免这种复杂性,而是使用存储库作为单元测试的边界。

© www.soinside.com 2019 - 2024. All rights reserved.