【问题标题】:Mocking Entity Framework Core context模拟实体框架核心上下文
【发布时间】:2018-05-13 05:26:26
【问题描述】:

我尝试测试我的应用,因此我需要模拟我的 EF 上下文。

我的代码似乎没问题,但我有以下异常:

"System.ArgumentNullException : 值不能为空。参数名称: 来源”

这是我的测试方法:

  var options = new DbContextOptionsBuilder<ProductContext>().Options;
    var settings = new SqlSettings
    {
        InMemory = true
    };

    var context = new Mock<ProductContext>(options, settings);
    var mockTreeService = new TreeService(context.Object);
    await mockTreeService.CreateTreeAsync("Testing tree", Guid.NewGuid());

    context.Verify(x => x.AddAsync(It.IsAny<Tree>(), CancellationToken.None), Times.Once);

貌似是在执行这段代码的时候抛出了这个异常

            var tree = await _context.Trees
                .Include(x => x.Translation)
                .FirstOrDefaultAsync(x => x.Translation.Pl == name);

它来自我正在测试的服务

【问题讨论】:

  • 在我看来“包含”会产生问题,但我不知道如何解决。
  • 您可能会发现EF Core Testing 文档很有用。
  • 您不需要在 EF 核心中模拟 ProductContext,而是使用 InMemory ProductContext

标签: c# entity-framework asp.net-core entity-framework-core


【解决方案1】:

我认为这是由于没有设置连接字符串。坦率地说,完全模拟出 DbContext 有点困难,这就是 EF Core 团队提供内存实现的原因。这更容易用于测试目的。只需将您的 options 初始化更改为:

var options = new DbContextOptionsBuilder<ProductContext>()
                  .UseInMemoryDatabase(Guid.NewGuid().ToString())
                  .Options;

之后,您需要使用测试数据填充数据库。然后,您可以运行其余的测试。

注意:如果您使用的是内存数据库,则不再需要模拟上下文,因此您可以删除那段代码。内存数据库本质上是一个模拟。

【讨论】:

  • 我在内存数据库中使用。它是通过在构造函数中传递 sql 设置在产品上下文中设置的
  • 没有。您将InMemory 设置为true,老实说,我认为这仅适用于SQLite。这不是一回事。您需要使用UseInMemoryDatabase(string)。
  • 为什么需要使用模拟?您设置了数据库的起始状态,执行一些操作,然后检查状态。如果状态是操作后的状态,则测试通过。它在上下文中调用一个或另一个方法这一事实是一个实现细节,从测试的角度来看并不重要。你测试的是结果而不是执行。
  • 再次,实现细节。无论如何,您不应该在这种情况下进行测试。如果底层逻辑发生变化,即使结果保持不变,您的测试也会失败。这是一个糟糕的测试。
  • 必须在我的测试项目中包含对 Microsoft.EntityFrameworkCore.InMemory 的引用!!
【解决方案2】:

我已经使用了这个https://github.com/huysentruitw/entity-framework-core-mock 库。非常简单,可以用更少的代码编写单元测试。

如果您使用的是 moq 框架,则可以使用大多数 Moq 方法。

以下是测试 DBQuery 的示例代码。

public async Task<Boat> GetByIdAsync(string id)
    => await _boatContext.Boats.Where(x => x.id == id).FirstOrDefaultAsync();

[Fact]
public async Task GetByIdAsync_WhenCalled_ReturnsItem()
{
    // Arrange
    var models = new[] { new Boat { id = "p1" } };
    var dbContextMock = new DbContextMock<BoatContext>();
    dbContextMock.CreateDbQueryMock(x => x.Boats, models);

    var service = new Properties(dbContextMock.Object);

    // Act
    var okResult = await service.GetByIdAsync("p1");

    // Assert
    Assert.IsType<Boat>(okResult.Result);
}

在这里发布这可能会对某人有所帮助:)

【讨论】:

    【解决方案3】:

    我认为 Mock 和 DbContext 不正确。在你的测试中你应该是mocking 你的repositories...mocking DbContext 你基本上是在测试Microsoft's 代码...这很愚蠢,因为他们已经这样做了。再说一遍……你所有的数据访问都应该通过repositories(见Repository Pattern),你应该是mocking那些在你的测试中,而不是DbContext。

    【讨论】:

    • 测试现成的代码,尤其是来自微软这样的分销商的代码通常是一种浪费。但是,在实体框架的情况下,有巨大的好处,因为您获得了测试配置的能力。
    • 我的天哪...我只想说...这是个好主意-docs.microsoft.com/en-us/ef/core/miscellaneous/testing/…。如果你还是不同意也没关系。我的想法。我从来没有说过或暗示你要测试你的回购之外的任何东西。我建议您通过模拟您的 DbContext 来测试您的存储库。模拟你的 dbcontext 对我来说是代码优先的。不同意?对不起,我无法说服你。只是我的观点。顺便说一句,当我说 Repo 时,我不是指你的 DbContext 类。我的意思是您的上下文的直接消费者。它在您的存储空间和您的“域”之间进行转换
    • 存储库模式的重点是为您的域处理持久层。 EF 已经这样做了。围绕 EF 包装存储库是多余且无用的。选择 EF 或任何其他 ORM 就是选择使用第三方 DAL 而不是自己编写。如果您还是要自己编写,那么转储 EF 并获得直接的 SQL。至少有一些意义,然后,您的应用程序可能会更容易维护和更高效。
    • 我不想重新引发之前的辩论,但我确实看到了嘲笑你的上下文的价值。当上下文以某种方式运行时,您希望您的存储库以某种方式运行。确保您的存储库对上下文正确“做出反应”的一种方法是模拟上下文的行为方式。您可以设置上下文以执行任何操作,并确保您的存储库在该场景中执行您希望它执行的操作。
    • 如果它们是被测代码的依赖项,您可能需要模拟您的存储库或 DbContext。它是存储库还是 DbContext 并不重要,它是一个依赖项,这就是你需要模拟它的原因。
    【解决方案4】:

    尝试使用我的 Moq/NSubstitute 扩展 MockQueryable:https://github.com/romantitov/MockQueryable 支持所有同步/异步操作

    //1 - create a List<T> with test items
    var users = new List<UserEntity>()
    {
     new UserEntity,
     ...
    };
    
    //2 - build mock by extension
    var mock = users.AsQueryable().BuildMock();
    
    //3 - setup the mock as Queryable for Moq
    _userRepository.Setup(x => x.GetQueryable()).Returns(mock.Object);
    
    //3 - setup the mock as Queryable for NSubstitute
    _userRepository.GetQueryable().Returns(mock);
    

    也支持 DbSet

    //2 - build mock by extension
    var mock = users.AsQueryable().BuildMockDbSet();
    
    //3 - setup DbSet for Moq
    var userRepository = new TestDbSetRepository(mock.Object);
    
    //3 - setup DbSet for NSubstitute
    var userRepository = new TestDbSetRepository(mock);
    

    注意:

    • 从 1.0.4 版本开始支持 AutoMapper
    • 从 1.1.0 版本开始支持 DbQuery

    【讨论】:

    • 我尝试了你的 NuGet 包,但我没有看到任何方法可以进行更复杂的设置。我需要使用连接测试查询,但我不知道如何告诉您的包涉及多个模拟 DbSet。 (只是来这里为您的包裹寻找替代品,很高兴在这里见到您。)
    • @KlomDark 感谢您的反馈。看到您无法使用 MockQueryable 来测试您的代码,我感到非常难过。我正在研究这个项目以使其变得更好。如果你发现 MockQueryable 不需要你的功能,你可以在 GitHub 页面上创建一个带有详细描述的问题。也欢迎您提出包含新功能的请求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-14
    • 1970-01-01
    • 1970-01-01
    • 2017-04-03
    • 2020-05-10
    相关资源
    最近更新 更多