dem*_*ron 12 c# unit-testing moq entity-framework-core asp.net-core
我正在测试一个使用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();
}
Run Code Online (Sandbox Code Playgroud)
我可以为一个测试进行设置,但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));
Run Code Online (Sandbox Code Playgroud)
这很好用。但是,如果我添加另一个存储库调用(例如Assert.DoesNotThrow(() => repository.Get(1);),我会得到
System.ObjectDisposedException: Cannot access a disposed context instance.
Run Code Online (Sandbox Code Playgroud)
如果我再次致电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));
Run Code Online (Sandbox Code Playgroud)
这是Get(int id)方法:
public T Get(int id)
{
using var db = ContextFactory.CreateDbContext();
return db.Set<T>().Find(id);
}
Run Code Online (Sandbox Code Playgroud)
据我了解,Mock 设置为返回
new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options)
Run Code Online (Sandbox Code Playgroud)
每次都.CreateDbContext()被叫到。对我来说,这意味着它每次都应该返回一个新的上下文实例,而不是已经处理过的实例。但是,看起来它正在返回相同的已处置实例。
pok*_*oke 25
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
Run Code Online (Sandbox Code Playgroud)
这将使用单个实例设置您的模拟。CreateDbContext每次在模拟上调用您的方法时都会返回此实例。由于您的方法在每次使用后(正确地)处理数据库上下文,因此第一次调用将处理此共享上下文,这意味着以后的每次调用都会返回CreateDbContext已处理的实例。
您可以通过传递一个工厂方法来更改它,Returns而不是每次创建一个新的数据库上下文:
mockDbFactory.Setup(f => f.CreateDbContext())
.Returns(() => new SomeDbContext(new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase("InMemoryTest")
.Options));
Run Code Online (Sandbox Code Playgroud)
对于像您这样的简单事情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);
}
}
Run Code Online (Sandbox Code Playgroud)
然后你可以直接在你的测试中使用它,这可能比在这种情况下处理模拟更具可读性:
var repository = new Repository<SomeEntity>(new TestDbContextFactory());
Run Code Online (Sandbox Code Playgroud)
除了@poke 的出色答案之外,这是我的完整答案,显示了添加测试数据。
在单元测试项目中安装这些 nuget 包:
像这样设置测试:
using Moq;
using Microsoft.EntityFrameworkCore;
namespace SomeProject.UnitTests;
public class SomeTests
{
[Fact]
public async Task Some_Test_Should_Do_Something()
{
// ARRANGE
var mockDbFactory = new Mock<IDbContextFactory<SomeDbContext>>();
var options = new DbContextOptionsBuilder<SomeDbContext>()
.UseInMemoryDatabase(databaseName: "SomeDatabaseInMemory")
.Options;
// Insert seed data into the database using an instance of the context
using (var context = new SomeDbContext(options))
{
context.SomeEntities.Add(new SomeEntity { Id = 1, SomeProp = "Some Prop Value" });
context.SomeEntities.Add(new SomeEntity { Id = 2, SomeProp = "Some Prop Value" });
context.SaveChanges();
}
// Now the in-memory db already has data, we don't have to seed everytime the factory returns the new DbContext:
mockDbFactory.Setup(f => f.CreateDbContextAsync(It.IsAny<CancellationToken>())).ReturnsAsync(() => new SomeDbContext(options));
// ACT
var serviceThatNeedsDbContextFactory = new SomeService(mockDbFactory.Object);
var result = await serviceThatNeedsDbContextFactory.MethodThatIsBeingTestedAsync();
// ASSERT
// Assert the result
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7341 次 |
| 最近记录: |