.NET Core 如何对服务进行单元测试?
.NET Core how to unit test service?
我已经构建了一个 WebAPI 并想创建一个单元测试项目来自动测试我的服务。
我的 WebAPI 的流程很简单:
控制器(DI 服务)-> 服务(DI 存储库)-> _repo CRUD
假设我有这样的服务:
public int Cancel(string id) //change status filed to 'n'
{
var item = _repo.Find(id);
item.status = "n";
_repo.Update(item);
return _repo.SaveChanges();
}
我想构建一个单元测试,它只使用 InMemoryDatabase。
public void Cancel_StatusShouldBeN() //Testing Cancel() method of a service
{
_service.Insert(item);
int rs = _service.Cancel(item.Id);
Assert.Equal(1, rs);
item = _service.GetByid(item.Id);
Assert.Equal("n", item.status);
}
我搜索了其他相关问题,发现
You can't use dependency injections on test classes.
我只想知道是否有任何其他解决方案可以实现我的单元测试想法?
进行单元测试时,您应该只提供要显式测试的 class 的所有依赖项。 即依赖注入;不是让服务自己构建依赖关系,而是让它依赖外部组件来提供它们。当您在依赖注入容器之外并在单元测试中时,您 正在手动创建您正在测试的 class,它是 您的 提供依赖项的责任。
实际上,这意味着您要么向构造函数提供模拟对象,要么提供实际对象。例如,您可能想要提供一个真实的记录器但没有目标、一个具有连接的内存数据库的真实数据库上下文或一些模拟服务。
假设您正在测试的服务如下所示:
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();
// using Moq as the mocking library
var utilityServiceMock = new Mock<UtilityService>();
utilityServiceMock.Setup(u => u.GetRandomNumber()).Returns(4);
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<Customer>().Add(new Customer()
{
Id = 2,
Name = "Foo bar"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new ExampleService(logger, db, utilityServiceMock.Object);
// act
var result = service.DoSomethingWithCustomer(2);
// assert
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();
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<SomeItem>().Add(new SomeItem()
{
Id = 5,
Status = "Not N"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new YourService(logger, db);
// act
var result = service.Cancel(5);
// assert
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);
}
}
顺便说一句。请注意,我一直在打开一个新的数据库上下文,以避免从缓存的实体中获取结果。通过打开一个新的上下文,我可以验证所做的更改是否确实完全进入了数据库。
我已经构建了一个 WebAPI 并想创建一个单元测试项目来自动测试我的服务。
我的 WebAPI 的流程很简单:
控制器(DI 服务)-> 服务(DI 存储库)-> _repo CRUD
假设我有这样的服务:
public int Cancel(string id) //change status filed to 'n'
{
var item = _repo.Find(id);
item.status = "n";
_repo.Update(item);
return _repo.SaveChanges();
}
我想构建一个单元测试,它只使用 InMemoryDatabase。
public void Cancel_StatusShouldBeN() //Testing Cancel() method of a service
{
_service.Insert(item);
int rs = _service.Cancel(item.Id);
Assert.Equal(1, rs);
item = _service.GetByid(item.Id);
Assert.Equal("n", item.status);
}
我搜索了其他相关问题,发现
You can't use dependency injections on test classes.
我只想知道是否有任何其他解决方案可以实现我的单元测试想法?
进行单元测试时,您应该只提供要显式测试的 class 的所有依赖项。 即依赖注入;不是让服务自己构建依赖关系,而是让它依赖外部组件来提供它们。当您在依赖注入容器之外并在单元测试中时,您 正在手动创建您正在测试的 class,它是 您的 提供依赖项的责任。
实际上,这意味着您要么向构造函数提供模拟对象,要么提供实际对象。例如,您可能想要提供一个真实的记录器但没有目标、一个具有连接的内存数据库的真实数据库上下文或一些模拟服务。
假设您正在测试的服务如下所示:
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();
// using Moq as the mocking library
var utilityServiceMock = new Mock<UtilityService>();
utilityServiceMock.Setup(u => u.GetRandomNumber()).Returns(4);
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<Customer>().Add(new Customer()
{
Id = 2,
Name = "Foo bar"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new ExampleService(logger, db, utilityServiceMock.Object);
// act
var result = service.DoSomethingWithCustomer(2);
// assert
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();
// arrange
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// fix up some data
db.Set<SomeItem>().Add(new SomeItem()
{
Id = 5,
Status = "Not N"
});
await db.SaveChangesAsync();
}
using (var db = new MyDbContext(dbOptionsBuilder.Options))
{
// create the service
var service = new YourService(logger, db);
// act
var result = service.Cancel(5);
// assert
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);
}
}
顺便说一句。请注意,我一直在打开一个新的数据库上下文,以避免从缓存的实体中获取结果。通过打开一个新的上下文,我可以验证所做的更改是否确实完全进入了数据库。