为 .Net Core 中的单元测试编写更好的装置
Writing better fixtures for unit tests in .Net Core
我对 .Net Core 中的单元测试还很陌生。我正在为 Interactor
class 编写单元测试,其职责是使用 IClientGateway
依赖项通过电子邮件或姓名获取 Client
s。我用过 XUnit
、Moq.AutoMock
和 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 = "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");
}
我对 .Net Core 中的单元测试还很陌生。我正在为 Interactor
class 编写单元测试,其职责是使用 IClientGateway
依赖项通过电子邮件或姓名获取 Client
s。我用过 XUnit
、Moq.AutoMock
和 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 = "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");
}