如何解决具有内存中 EF 核心的 Moq IDBContextFactory
我正在测试一个使用 DbContext
的类。该类注入了一个 IDbContextFactory
,然后用于获取 DbContext
:
protected readonly IDbContextFactory<SomeDbContext> ContextFactory;
public Repository(IDbContextFactory<SomeDbContext> contextFactory)
{
ContextFactory = contextFactory;
}
public List<T> Get()
{
using var db = ContextFactory.CreateDbContext();
return db.Set<T>().ToList();
}
我可以为一次测试进行设置,但每次我想使用上下文时都必须调用 Mock<DbContextFactory>.Setup(f => f.CreateDbContext())
方法。
这是一个例子:
var mockDbFactory = new Mock<IDbContextFactory<SomeDbContext>>();
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
var repository = new Repository<SomeEntity>(mockDbFactory.Object);
// non-existent id
Assert.IsNull(repository.Get(-1));
这很好用。但是,如果我添加另一个回购调用(如 Assert.DoesNotthrow(() => repository.Get(1);
),我得到
System.ObjectdisposedException: Cannot access a disposed context instance.
如果我再次调用 Mock<T>.Setup()
,一切正常
var mockDbFactory = new Mock<IDbContextFactory<SomeDbContext>>();
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
var repository = new Repository<SomeEntity>(mockDbFactory.Object);
// non-existent id
Assert.IsNull(repository.Get(-1));
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
// pass
Assert.DoesNotthrow(() => repository.Get(1));
这是 Get(int id)
方法:
public T Get(int id)
{
using var db = ContextFactory.CreateDbContext();
return db.Set<T>().Find(id);
}
据我所知,Mock 已准备好返回
new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options)
每次调用 .CreateDbContext()
时。对我来说,这意味着它每次都应该返回一个新的上下文实例,而不是已经处理过的实例。但是,它似乎返回了相同的已处理实例。
解决方法
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
这将使用单个实例设置您的模拟。每次在模拟上调用 CreateDbContext
方法时都会返回此实例。由于您的方法(正确)在每次使用后处理数据库上下文,第一次调用将处理此共享上下文,这意味着以后每次调用 CreateDbContext
都会返回已处理的实例。
您可以通过将工厂方法传递给 Returns
来更改此设置,而不是每次都创建一个新的数据库上下文:
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(() => new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
对于像您的 IDbContextFactory<>
这样的简单事物,假设它只有一个 CreateDbContext
方法,您也可以只创建一个真正的测试实现,而不是每次都创建模拟:
public class TestDbContextFactory : IDbContextFactory<SomeDbContext>
{
private DbContextOptions<SomeDbContext> _options;
public TestDbContextFactory(string databaseName = "InMemoryTest")
{
_options = new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase(databaseName)
.Options;
}
public SomeDbContext CreateDbContext()
{
return new SomeDbContext(_options);
}
}
然后你可以直接在你的测试中使用它,这可能比在这种情况下必须处理模拟更具可读性:
var repository = new Repository<SomeEntity>(new TestDbContextFactory());
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。