【问题标题】:How can I use InMemory database to check records added correctly in a unit test如何使用 InMemory 数据库检查单元测试中正确添加的记录
【发布时间】:2021-03-24 14:30:18
【问题描述】:

使用InMemory 数据库我正在尝试编写一个单元测试,它将检查一个方法是否正在向表中添加一条记录。记录被添加到被测类中,但是当我尝试重新创建时,我得到了上下文处理错误。设置此测试的正确方法是什么,以便在处理上下文后检查内存中的表?

单元测试

[Fact]
public void Test_AddContact_AddsSuccessfully()
{
    var contextFactoryMock = new Mock<IContextFactory>();
    contextFactoryMock.Setup(x => x.CreateContext()).Returns(CreateInMemoryContactContext());

    var classUnderTest = new AddContact(contextFactoryMock.Object); 

    var response = classUnderTest.Run(new UkContactUsDto());

    using (var ctx = contextFactoryMock.Object.CreateContext()) 
    {
        var items = ctx.ContactUs.ToList(); //<--- Exception happens here due to ctx being disposed
    }
}

创建 InMemory 方法

private ContactContext CreateInMemoryContactContext()
{
    var _inMemoryContext = new ContactContext(new DbContextOptionsBuilder<ContactContext>().UseInMemoryDatabase((Guid.NewGuid().ToString())).Options);
    
    return _inMemoryContext;
}

方法即时测试

try
{
    await using var ctx = _contextFactory.CreateContext();
    ctx.ContactUs.Add(contactUs);
    ctx.SaveChanges();
}
catch (Exception ex)
{
    log.LogInformation(ex,"An error occured during contact us insertion.");
    throw;
}

错误信息

无法访问已释放的上下文实例。造成这种情况的一个常见原因 错误正在处理已解决的上下文实例 依赖注入,然后尝试使用相同的上下文 在您的应用程序的其他地方实例。如果您是 在上下文实例上调用“Dispose”,或将其包装在 using 陈述。如果你使用依赖注入,你应该让 依赖注入容器负责处理上下文 实例。

【问题讨论】:

  • 我认为您的单元测试被测类将在这里使用不同的上下文。您的工厂将创建两个不同的上下文。我会先尝试生成一个上下文 (using ctx = contextFactoryMock.Object.CreateContext()), then mock the factory with: contextFactoryMock.Setup(x => x.CreateContext()).Returns(ctx);` 然后检查添加的联系人,检查你的初始 ctx

标签: c# unit-testing entity-framework-core


【解决方案1】:

您在正在测试的方法中处理上下文,因此您不能再次使用它。一种方法是创建一个新的上下文,但您需要手动跟踪InMemoryDatabaseRoot 实例。例如:

private static readonly _memoryDatabaseRoot InMemoryDatabaseRoot = new InMemoryDatabaseRoot();

private ContactContext CreateInMemoryContactContext()
{
    var _inMemoryContext = new ContactContext(new DbContextOptionsBuilder<ContactContext>()
        .UseInMemoryDatabase("my-database", _memoryDatabaseRoot).Options);
    
    return _inMemoryContext;
}

现在在您的测试中,您可以调用 CreateInMemoryContactContext 来获取使用相同内存数据库实例的新上下文

注意:如果您要在多个测试中使用它,您可能不希望根是静态的。

【讨论】:

  • 这是否意味着私有静态只读 InMemoryDatabaseRoot _memoryDatabaseRoot = new InMemoryDatabaseRoot();
  • 使用static,数据将在每次测试中重复使用。如果您希望每次测试都有新的样本数据,可以删除static
  • @JsonStatham 不需要是static,您只需要确保在创建新上下文时使用相同的实例进行测试。如果这有助于您的思考,您可以将根对象视为您的数据库。
【解决方案2】:

在 Mock 中,方法 CreateContext 使用 DbContext 的实例进行模拟。 Mock 为所有对CreateContext 的调用返回相同的实例。您可以通过此测试进行检查:

var context = CreateInMemoryContactContext();
var contextFactoryMock = new Mock<IContextFactory>();
contextFactoryMock.Setup(x => x.CreateContext()).Returns(context);
Assert.ReferenceEquals(context, contextFactoryMock.Object.CreateContext());

想想AddContact 处理上下文。当你重用上下文做断言时,上下文就被释放了。

您需要通过Returns 中的工厂,例如:

var contextFactoryMock = new Mock<IContextFactory>();
contextFactoryMock.Setup(x => x.CreateContext()).Returns(CreateInMemoryContactContext);

为了在上下文之间保存数据,您需要在创建新的 DbContext 时重用相同的 DbOption。使用 xunit,对于每个测试,都会实例化一个测试类的实例。您可以将初始化部分放在构造函数中:

public class AddContactTest
{
    private DbOptions _dbOptions;

    public AddContactTest()
    {
        _options = new DbContextOptionsBuilder<ContactContext>().UseInMemoryDatabase((Guid.NewGuid().ToString())).Options
    }

    private ContactContext CreateInMemoryContactContext()
    {
        var _inMemoryContext = new ContactContext(_dbOptions);
        return _inMemoryContext;
    }

    [Fact]
    public void Test_AddContact_AddsSuccessfully()
    {
        var contextFactoryMock = new Mock<IContextFactory>();
        contextFactoryMock.Setup(x => x.CreateContext()).Returns(CreateInMemoryContactContext);

        var classUnderTest = new AddContact(contextFactoryMock.Object); 

        var response = classUnderTest.Run(new UkContactUsDto());

        using (var ctx = contextFactoryMock.Object.CreateContext()) 
        {
            var items = ctx.ContactUs.ToList();
        }
    }
}

【讨论】:

  • 但是这样做会在测试插入记录时创建一个新的上下文,并且使用内存数据库,数据将会消失。
  • 这是正确的,因为我已经尝试过,但联系我们的表格是空的
  • 我错过了这部分...您可以打开和关闭上下文而不会丢失数据,但您需要使用相同的 DbOptions。但是CreateInMemoryContactContext 方法创建了一个新的DbOption。我将编辑我的答案。
猜你喜欢
  • 2015-10-28
  • 1970-01-01
  • 1970-01-01
  • 2016-12-18
  • 2016-12-17
  • 1970-01-01
  • 2013-04-13
  • 2020-11-09
  • 1970-01-01
相关资源
最近更新 更多