概述:在这篇文章中,我们将介绍如何创建和管理模拟 API,以便使用 WireMock.NET 进行集成测试。集成测试保证了软件系统内不同组件之间的无缝通信,并且识别并解决了任何潜在问题。这种测试方法对于验证我们的应用程序与内部和外部 API 之间的交互至关重要,确保遵守预定义的合同和所需的功能。WireMock.NET 是一个强大的工具包,用于复制 HTTP API 行为,非常适合在各种场景中模拟 API。无论是测试依赖于 API 的类,还是使用外部 API 进行集成测试,WireMock.NET 都能提供全面的功能来有效模拟 API 行为。为什么我们需要模拟 API?我们需要模拟外部 API 的多
在这篇文章中,我们将介绍如何创建和管理模拟 API,以便使用 WireMock.NET 进行集成测试。集成测试保证了软件系统内不同组件之间的无缝通信,并且识别并解决了任何潜在问题。这种测试方法对于验证我们的应用程序与内部和外部 API 之间的交互至关重要,确保遵守预定义的合同和所需的功能。
WireMock.NET 是一个强大的工具包,用于复制 HTTP API 行为,非常适合在各种场景中模拟 API。无论是测试依赖于 API 的类,还是使用外部 API 进行集成测试,WireMock.NET 都能提供全面的功能来有效模拟 API 行为。
为什么我们需要模拟 API? 我们需要模拟外部 API 的多种方案,如下所述:
-
外部 API 可能仍在开发中。尽管我们已经就详细说明 URL 结构和有效负载格式等内容的合同达成一致,但我们仍然需要满足最后期限。因此,即使在外部 API 完全运行之前,我们也需要确保我们的逻辑正常运行。
-
有时,外部 API 可能缺少专用的测试环境。在这种情况下,模拟这些 API 并在本地运行测试以验证应用程序的行为是可行的。
-
构建健壮的软件涉及处理各种异常情况。但是,并非所有 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 是一个非常强大的工具,我们将在以后的博客中看到它如何帮助我们进行负载测试。