为 .Net Core 中的单元测试编写更好的装置

Writing better fixtures for unit tests in .Net Core

我对 .Net Core 中的单元测试还很陌生。我正在为 Interactor class 编写单元测试,其职责是使用 IClientGateway 依赖项通过电子邮件或姓名获取 Clients。我用过 XUnitMoq.AutoMockFluentAssertions.

GetClientsInteractorTest.cs

[Trait("Clients", "GetClientsInteractor")]
[Collection(nameof(GetClientsInteractorTestsCollectionFixture))]
public class GetClientsInteractorTests
{
    private readonly GetClientsInteractorTestsFixture fixture;

    public GetClientsInteractorTests(GetClientsInteractorTestsFixture fixture) =>
        this.fixture = fixture;

    [Theory(DisplayName = "should return clients in clientsGateway")]
    [MemberData(nameof(GetClientsInteractorTestsFixture.Params), MemberType = typeof(GetClientsInteractorTestsFixture))]
    public async Task ShouldReturnClients(IEnumerable<Client> mockedClients, CancellationTokenSource cancellationTokenSource,
                                            GetClientsRequest request, GetClientsResponse expectedResponse)
    {
        // Arrange
        fixture.Mocker.GetMock<IClientGateway>()
            .Setup(ClientGateway => ClientGateway.GetByNameAsync(request.Name, cancellationTokenSource.Token))
            .ReturnsAsync(mockedClients.Where(client => client.Name.Equals(request.Name)))
            .Verifiable();
        fixture.Mocker.GetMock<IClientGateway>()
            .Setup(ClientGateway => ClientGateway.GetByEmailAsync(request.Email, cancellationTokenSource.Token))
            .ReturnsAsync(mockedClients.Where(client => client.Email.Equals(request.Email)))
            .Verifiable();
        // Act
        GetClientsResponse actualResponse =
            await fixture.Mocker.CreateInstance<GetClientsInteractor>()
            .Handle(request, cancellationTokenSource.Token);
        // Assert
        if(request.Name != null)
            fixture.Mocker.GetMock<IClientGateway>()
                .Verify(mock => mock.GetByNameAsync(request.Name, cancellationTokenSource.Token), Times.Once(), "GetByNameAsync was not called");
        else if (request.Email != null)
            fixture.Mocker.GetMock<IClientGateway>()
                .Verify(mock => mock.GetByEmailAsync(request.Email, cancellationTokenSource.Token), Times.Once(), "GetByEmailAsync was not called");
        actualResponse.Should()
            .Be(expectedResponse);
    }
}

GetClientsInteractorTestsFixture.cs

public class GetClientsInteractorTestsFixture
{
    public AutoMocker Mocker = new AutoMocker(MockBehavior.Strict);

    public static IEnumerable<Client> mockedClients = new Client[] {
            new Client()
            {
                ClientId = 1,
                Name = "Victor",
                Email = "someEmail@someProvider.com"
            },
        };

    public static CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    public static IEnumerable<object[]> Params = new[]
    {
        new object[]
        {
            mockedClients,
            cancellationTokenSource,
            new GetClientsRequest(),
            new GetClientsResponse()
        },

        new object[]
        {
            mockedClients,
            cancellationTokenSource,
            new GetClientsRequest(){ Name = "Victor" },
            new GetClientsResponse(){ Clients = new List<ClientDto>()
                {
                    new ClientDto()
                    {
                        Id = 1,
                        Name = "Victor",
                        Email = "someEmail@someProvider.com"
                    }
                }
            }
        },

        new object[]
        {
            mockedClients,
            cancellationTokenSource,
            new GetClientsRequest(){ Email = "someEmail@someProvider.com"},
            new GetClientsResponse(){ Clients = new List<ClientDto>()
                {
                    new ClientDto()
                    {
                        Id = 1,
                        Name = "Victor",
                        Email = "someEmail@someProvider.com"
                    }
                }
            }
        },
    };
}

单元测试本身 (ShouldReturnClients) 一开始很简单,但它需要 4 个参数(加上 AutoMocker)作为测试夹具,可在 GetClientsInteractorTestsFixture 中找到。有没有更好的方法来减少重复性来制作这个测试夹具?也许使用 AutoFixture?

简短的回答是 - 是的,AutoFixture 可以帮助您减少样板文件并保持测试干燥。 以下是您使用 AutoFixture 进行的测试可能看起来的样子。 有关使用 AutoFixture 的更多详细信息,您可以访问 documentation, check out the Pluralsight course, read about AutoFixture on Mark Seeman's blog, or seek help on AutoFixture's GitHub Q&A section.

[Theory]
[MemberAutoDomainData(nameof(GetClientsInteractorTestsFixture.Params))]
public async Task ReturnsExpectedResponse(
    GetClientsRequest request,
    GetClientsResponse expectedResponse,
    GetClientsInteractor sut)
{
    var actual = await sut.Handle(request, default);

    actual.Should().Be(expectedResponse);
}

[Theory]
[MemberAutoDomainData(nameof(GetClientsInteractorTestsFixture.ParamsWithName))]
public async Task RequestsClientsByNameWhenRequestContainsName(
    [Frozen] Mock<IClientGateway> gateway,
    GetClientsRequest request,
    GetClientsInteractor sut)
{
    await sut.Handle(request, default);

    gateway.Verify(
        x => x.GetByNameAsync(request.Name, It.IsAny<CancellationToken>()),
        Times.Once(), "GetByNameAsync was not called");
}

[Theory]
[MemberAutoDomainData(nameof(GetClientsInteractorTestsFixture.Params))]
public async Task RequestsClientsByNameWhenRequestNameEmpty(
    [Frozen] Mock<IClientGateway> gateway,
    GetClientsRequest request,
    GetClientsInteractor sut)
{
    await sut.Handle(request, default);

    gateway.Verify(
        x => x.GetByEmailAsync(request.Email, It.IsAny<CancellationToken>()),
        Times.Once(), "GetByEmailAsync was not called");
}