Search code examples
c#unit-testingmoqxunitautofixture

Writing better fixtures for unit tests in .Net Core


I'm fairly new to unit testing in the .Net Core. I'm writing unit tests for an Interactor class, whose responsibility is to getClients either by email or name using the IClientGateway dependency. I've used XUnit, Moq.AutoMock and FluentAssertions.

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 = "[email protected]"
            },
        };

    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 = "[email protected]"
                    }
                }
            }
        },

        new object[]
        {
            mockedClients,
            cancellationTokenSource,
            new GetClientsRequest(){ Email = "[email protected]"},
            new GetClientsResponse(){ Clients = new List<ClientDto>()
                {
                    new ClientDto()
                    {
                        Id = 1,
                        Name = "Victor",
                        Email = "[email protected]"
                    }
                }
            }
        },
    };
}

The unit test itself (ShouldReturnClients) its quite simple at first, but it requires 4 parameters (plus AutoMocker) as a test fixture, found in GetClientsInteractorTestsFixture. Is there a better way to make this test fixture by making it less repetitive? perhaps using something like AutoFixture?


Solution

  • The short answer is - Yes, AutoFixture can help you reduce the boilerplate and keep your tests DRY. Below is what you tests could potentially look like with AutoFixture. For more details on using AutoFixture, you can visit the 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");
    }