在单元测试时,你应该明确地提供正在测试的类的所有依赖项。那就是依赖注入;不是让服务自己构造其依赖项,而是让它依赖外部组件来提供它们。当你在依赖注入容器之外,在一个单元测试中手动创建正在测试的类时,你需要负责提供依赖项。
实践上,这意味着你要么提供模拟对象,要么提供实际对象给构造函数。例如,你可能想提供一个真正的记录器但没有目标,一个具有连接的内存数据库的真正数据库上下文,或者一些模拟的服务。
假设在这个例子中,你正在测试的服务看起来像这样:
public class ExampleService
{
public ExampleService(ILogger<ExampleService> logger,
MyDbContext databaseContext,
UtilityService utilityService)
{
}
}
因此,为了测试ExampleService
,我们需要提供这三个对象。在这种情况下,我们将为每个对象执行以下操作:
ILogger<ExampleService>
- 我们将使用一个真正的日志记录器,没有任何附加的目标。因此,任何对记录器的调用都将正常工作,而无需提供某些模拟对象,但我们不需要测试日志输出,因此我们不需要一个真正的目标。
MyDbContext
- 在这里,我们将使用带有内存数据库的真实数据库上下文。
UtilityService
- 对于这个,我们将创建一个只设置我们想要测试的方法内部需要的实用方法的模拟对象。
因此,一个单元测试可能看起来像这样:
[Fact]
public async Task TestExampleMethod()
{
var logger = new LoggerFactory().CreateLogger<ExampleService>();
var dbOptionsBuilder = new DbContextOptionsBuilder().UseInMemoryDatabase();
var utilityServiceMock = new Mock<UtilityService>();
utilityServiceMock.Setup(u => u.GetRandomNumber()).Returns(4);
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
db.Set<Customer>().Add(new Customer()
{
Id = 2,
Name = "Foo bar"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
var service = new ExampleService(logger, db, utilityServiceMock.Object);
var result = service.DoSomethingWithCustomer(2);
Assert.NotNull(result);
Assert.Equal(2, result.CustomerId);
Assert.Equal("Foo bar", result.CustomerName);
Assert.Equal(4, result.SomeRandomNumber);
}
}
在您特定的Cancel
情况下,您希望避免使用任何当前未测试的服务方法。因此,如果您想测试Cancel
,则应该从您的服务中调用的唯一方法是Cancel
。一个测试可能会像这样(只是猜测这里的依赖关系):
[Fact]
public async Task Cancel_StatusShouldBeN()
{
var logger = new LoggerFactory().CreateLogger<ExampleService>();
var dbOptionsBuilder = new DbContextOptionsBuilder().UseInMemoryDatabase();
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
db.Set<SomeItem>().Add(new SomeItem()
{
Id = 5,
Status = "Not N"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
var service = new YourService(logger, db);
var result = service.Cancel(5);
Assert.Equal(1, result);
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
var item = db.Set<SomeItem>().Find(5);
Assert.Equal(5, item.Id);
Assert.Equal("n", item.Status);
}
}
顺带提一下,我总是打开一个新的数据库上下文来避免从缓存实体中获取结果。通过打开一个新的上下文,我可以验证更改是否已完全传输到数据库中。
DbContext
实现。一个单元测试可以在没有外部依赖的情况下进行测试(例如通过模拟接口)。这是当你在服务中使用EF Core而没有将其抽象到存储库或通过CQRS时的缺点之一。 - Tseng