c# - .NET Core 如何对服务进行单元测试?

标签 c# asp.net-core .net-core xunit.net

我已经构建了一个 WebAPI,并希望创建一个单元测试项目来自动测试我的服务。

我的 WebAPI 的流程很简单:

Controller (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.

我只是想知道是否有任何其他解决方案可以实现我的单元测试想法?

最佳答案

进行单元测试时,您应该明确提供您正在测试的类的所有依赖项。 依赖注入(inject);不是让服务自己构建依赖关系,而是让它依赖外部组件来提供它们。当您在依赖项注入(inject)容器之外并在手动创建您正在测试的类的单元测试中时,有责任提供依赖项。

实际上,这意味着您要么向构造函数提供模拟对象,要么提供实际对象。例如,您可能想要提供一个真实的记录器但没有目标、一个具有连接的内存数据库的真实数据库上下文或一些模拟服务。

假设您正在测试的服务如下所示:

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);
    }
}

顺便说一句。请注意,我一直在打开一个新的数据库上下文,以避免从缓存的实体中获取结果。通过打开一个新的上下文,我可以验证所做的更改是否确实完全进入了数据库。

关于c# - .NET Core 如何对服务进行单元测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45409622/

相关文章:

c# - WPF DataContext 在看似相同的情况下工作方式不同

c# - 如何使用必需的非空列设置 filehelpers

ssl - 使用 .NET Core 与 MailKit 设置 SSL 握手时出现 CryptographicException 异常

amazon-web-services - 将 ASP.NET Core 部署到 AWS Elastic Beanstalk,必须包含一个带有 '.runtimeconfig.json' 后缀错误的文件

c# - 如何在请求结束时释放资源并在 ASP.NET 5/Core 中处理注入(inject)的服务?

azure - 如何使用 Azure Cache for Redis 中的 RedisTimeSeries 模块?

c# - 分布式事务 : . NET Framework 与 .NET Core

c# - 帮助我找到更好的编程方法

c# - 从 PC 以编程方式访问 Android 设备上的文件

c# - 如何从 .Net Core 连接到 Oracle 数据库连接