简体   繁体   English

.NET Core如何进行单元测试服务?

[英].NET Core how to unit test service?

I have build a WebAPI and want to create a unit test project to have my services tested automatically. 我已经构建了一个WebAPI,并希望创建一个单元测试项目来自动测试我的服务。

The flow of my WebAPI is simple: 我的WebAPI流程很简单:

Controller (DI Service) -> Service (DI Repository) -> _repo CRUD 控制器(DI服务) - >服务(DI存储库) - > _repo CRUD

Suppose I have a service like: 假设我有这样的服务:

public int Cancel(string id) //change status filed to 'n'
{
    var item = _repo.Find(id);
    item.status = "n";
    _repo.Update(item);
    return _repo.SaveChanges();
}

And I want to build a unit test, which just use InMemoryDatabase. 我想构建一个单元测试,它只使用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);
}

I've searched other related question, found that 我搜索了其他相关问题,发现了

You can't use dependency injections on test classes. 您不能在测试类上使用依赖注入。

I just want to know if there is any other solution to achive my unit test idea? 我只是想知道是否有任何其他解决方案可以实现我的单元测试理念?

When unit testing, you should just supply all the dependencies of the class you are testing explicitly. 在单元测试时,您应该只提供您正在测试的类的所有依赖项。 That is dependency injection; 这是依赖注入; not having the service construct its dependencies on its own but making it rely on the outer component to provide them. 没有服务构建它自己的依赖,但让它依赖外部组件来提供它们。 When you are outside of a dependency injection container and inside a unit test where you are manually creating the class you are testing, it's your responsibility to provide the dependencies. 如果你是一个依赖注入容器的外面,单元测试, 手动创建你测试的类里面,这是你的责任,提供的依赖关系。

In practice, this means that you either provide mocks or actual objects to the constructor. 实际上,这意味着您要么为构造函数提供模拟或实际对象。 For example, you might want to provide a real logger but without a target, a real database context with a connected in-memory database, or some mocked service. 例如,您可能希望提供一个真正的记录器但没有目标,一个具有连接的内存数据库的真实数据库上下文,或一些模拟服务。

Let's assume for this example, that the service you are testing looks like this: 让我们假设在这个例子中,您正在测试的服务如下所示:

public class ExampleService
{
    public ExampleService(ILogger<ExampleService> logger,
        MyDbContext databaseContext,
        UtilityService utilityService)
    {
        // …
    }
    // …
}

So in order to test ExampleService , we need to provide those three objects. 因此,为了测试ExampleService ,我们需要提供这三个对象。 In this case, we will do the following for each: 在这种情况下,我们将针对每个进行以下操作:

  • ILogger<ExampleService> – we will use a real logger, without any attached target. ILogger<ExampleService> - 我们将使用真正的记录器,没有任何附加目标。 So any call on the logger will work properly without us having to provide some mock, but we do not need to test the log output, so we do not need a real target 因此,对记录器的任何调用都将正常工作,而我们不必提供一些模拟,但我们不需要测试日志输出,因此我们不需要真正的目标
  • MyDbContext – Here, we'll use the real database context with an attached in-memory database MyDbContext - 在这里,我们将使用附加的内存数据库中的真实数据库上下文
  • UtilityService – For this, we will create a mock which just setups the utility method we need inside the methods we want to test. UtilityService - 为此,我们将创建一个mock,它只是在我们想要测试的方法中设置我们需要的实用程序方法。

So a unit test could look like this: 所以单元测试看起来像这样:

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

In your specific Cancel case, you want to avoid using any methods of the service you are not currently testing. 在您的特定Cancel情况下,您希望避免使用您当前未测试的任何服务方法。 So if you want to test Cancel , the only method you should call from your service is Cancel . 因此,如果您要测试Cancel ,您应该从服务中调用的唯一方法是Cancel A test could look like this (just guessing the dependencies here): 测试看起来像这样(只是在这里猜测依赖关系):

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

Btw. 顺便说一句。 note that I'm opening up a new database context all the time in order to avoid getting results from the cached entities. 请注意,我一直在打开一个新的数据库上下文,以避免从缓存的实体中获取结果。 By opening a new context, I can verify that the changes actually made it into the database completely. 通过打开一个新的上下文,我可以验证这些更改是否真正完全进入了数据库。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM