在这篇文章中,我们将介绍如何创建和管理模拟 API,以便使用 WireMock.NET 进行集成测试。集成测试保证了软件系统内不同组件之间的无缝通信,并且识别并解决了任何潜在问题。这种测试方法对于验证我们的应用程序与内部和外部 API 之间的交互至关重要,确保遵守预定义的合同和所需的功能。
WireMock.NET 是一个强大的工具包,用于复制 HTTP API 行为,非常适合在各种场景中模拟 API。无论是测试依赖于 API 的类,还是使用外部 API 进行集成测试,WireMock.NET 都能提供全面的功能来有效模拟 API 行为。
为什么我们需要模拟 API? 我们需要模拟外部 API 的多种方案,如下所述:
现在我们已经了解了为什么我们需要模拟 API 进行集成测试,让我们编写代码并了解如何使用 WireMock.NET。WireMock 是用于模拟 API 的流行工具,而 WireMock.NET 是为 .NET 量身定制的实现。
代码演练 让我们设想一个场景:我们正在为一家房地产公司开发软件。作为此项目的一部分,我们的任务是创建一个 API 来检索居民的所有未结余额。为了实现这一点,我们的 API 需要与公用事业公司 API 交互,以获取公用事业的欠款。我们将此交互封装在一个 UtilityService.cs 类中,负责对外部 Utility Company API 进行必要的调用并处理响应。
一旦我们实现了 UtilityService,我们的下一步就是通过集成测试来验证其功能。由于公用事业公司 API 可能不容易用于测试,因此我们将使用 WireMock.NET 来模拟其行为。这个工具允许我们创建模拟服务器来模拟实际 API 的响应,使我们能够在各种场景下彻底测试我们的 UtilityService。通过这些集成测试,我们将确保我们的 API 按预期运行,并准确地从公用事业公司 API 检索和处理居民余额信息。
using System.Net;
using System.Text.Json;
namespace RealEstate.Business.Utilities
{
public class UtilityService
{
private readonly HttpClient _httpClient;
public UtilityService(HttpClient httpClient)
{
_httpClient = httpClient ??
throw new ArgumentNullException(nameof(httpClient));
}
public async Task<ResidentUtilityDto?> GetResidentUtilityBalanceByIdAsync(int customerId)
{
var response = await _httpClient.GetAsync($"/balances/v2/{customerId}");
if (response.StatusCode == HttpStatusCode.OK)
{
try
{
return JsonSerializer.Deserialize<ResidentUtilityDto>(await response.Content.ReadAsStringAsync());
}
catch
{
return null;
}
}
return null;
}
}
}
我们将看到对 WireMock 进行建模以模拟案例的不同方法。
案例
[Test]
public async Task GivenValidResident_WhenGetResidentUtilityBalanceByIdAsyncIsInvoked_ThenValidResidentUtilityBalanceIsReturned()
{
//Arrange
var customerId = 23;
var faker = new ResidentUtilityDtoFaker(customerId);
var residentBalance = faker.Generate();
_mockServer.Given(Request.Create().UsingGet().WithPath($"/balances/v2/{customerId}"))
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK).WithBodyAsJson(residentBalance));
//Act
var balanceResponse = await _utilityService.GetResidentUtilityBalanceByIdAsync(customerId);
//Assert
balanceResponse.Should().NotBeNull();
balanceResponse.ElectricityBalance.Should().Be(residentBalance.ElectricityBalance);
balanceResponse.TrashBalance.Should().Be(residentBalance.TrashBalance);
balanceResponse.WaterBalance.Should().Be(residentBalance.WaterBalance);
}
案例
[Test]
public async Task GivenInValidResident_WhenGetResidentUtilityBalanceByIdAsyncIsInvoked_ThenNullIsReturned()
{
//Arrange
var customerId = 42;
_mockServer.Given(Request.Create().UsingGet().WithPath($"/balances/v2/{customerId}"))
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.NotFound));
//Act
var balanceResponse = await _utilityService.GetResidentUtilityBalanceByIdAsync(customerId);
//Assert
balanceResponse.Should().BeNull();
}
延迟响应
模拟延迟以响应模拟真实世界的场景。
[Test]
public async Task GivenValidResident_WhenGetResidentUtilityBalanceByIdAsyncIsInvoked_ShouldHandleDelayedResponses()
{
//Arrange
var customerId = 23;
var faker = new ResidentUtilityDtoFaker(customerId);
var residentBalance = faker.Generate();
_mockServer.Given
(Request.Create().UsingGet().WithPath($"/balances/v2/{customerId}"))
.RespondWith(Response.Create().WithStatusCode(HttpStatusCode.OK)
.WithBodyAsJson(residentBalance).WithDelay(TimeSpan.FromMilliseconds(200)));
//Act
var watch = new Stopwatch();
watch.Start();
var balanceResponse = await _utilityService.GetResidentUtilityBalanceByIdAsync(customerId);
watch.Stop();
//Assert
balanceResponse.Should().NotBeNull();
watch.ElapsedMilliseconds.Should().BeGreaterThan(0);
}
模拟部分响应等故障,并了解我们如何处理代码内。
[Test]
public async Task GivenValidResident_WhenGetResidentUtilityBalanceByIdAsyncIsInvoked_ShouldHandleFaults()
{
//Arrange
var customerId = 23;
var faker = new ResidentUtilityDtoFaker(customerId);
var residentBalance = faker.Generate();
_mockServer.Given(Request.Create().UsingGet().WithPath($"/balances/v2/{customerId}"))
.RespondWith(Response.Create().WithFault(FaultType.MALFORMED_RESPONSE_CHUNK));
//Act
var balanceResponse = await _utilityService.GetResidentUtilityBalanceByIdAsync(customerId);
//Assert
balanceResponse.Should().BeNull();
}
还有更多用途,例如标头匹配、设置请求的优先级,甚至我们可以模拟 Webhook 等。WireMock.Net 是一个非常强大的工具,我们将在以后的博客中看到它如何帮助我们进行负载测试。