提问者:小点点

如何使用InMemory数据库检查单元测试中正确添加的记录


使用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;
}

方法im测试

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语句中,则可能会出现这种情况。如果您正在使用依赖项注入,则应该让依赖项注入容器负责处理上下文实例。


共2个答案

匿名用户

您正在处理正在测试的方法中的上下文,这样您就不能再使用它了。解决这一问题的方法之一是创建一个新上下文,但您需要手动跟踪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来获取使用相同内存中数据库实例的新上下文

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

匿名用户

在模拟中,createContext方法是用dbcontext的实例模拟的。对于所有对createContext的调用,模拟返回相同的实例。您可以使用以下测试进行检查:

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

在thinkaddcontact中处理上下文。当您重用上下文来执行断言时,上下文将被释放。

您需要在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();
        }
    }
}