可以在单个集成测试中托管 WebApplicationFactory 的多个通信实例。
假设我们有名为WebApplication 的主服务,它依赖于名为WebService 的实用服务,使用名为“WebService”的命名HttpClient。
以下是集成测试示例:
[Fact]
public async Task GetWeatherForecast_ShouldReturnSuccessResult()
{
// Create application factories for master and utility services and corresponding HTTP clients
var webApplicationFactory = new CustomWebApplicationFactory();
var webApplicationClient = webApplicationFactory.CreateClient();
var webServiceFactory = new WebApplicationFactory<Startup>();
var webServiceClient = webServiceFactory.CreateClient();
// Mock dependency on utility service by replacing named HTTP client
webApplicationFactory.AddHttpClient(clientName: "WebService", webServiceClient);
// Perform test request
var response = await webApplicationClient.GetAsync("weatherForecast");
// Assert the result
response.EnsureSuccessStatusCode();
var forecast = await response.Content.ReadAsAsync<IEnumerable<WeatherForecast>>();
Assert.Equal(10, forecast.Count());
}
此代码需要实现CustomWebApplicationFactory 类:
// Extends WebApplicationFactory allowing to replace named HTTP clients
internal sealed class CustomWebApplicationFactory
: WebApplicationFactory<WebApplication.Startup>
{
// Contains replaced named HTTP clients
private ConcurrentDictionary<string, HttpClient> HttpClients { get; } =
new ConcurrentDictionary<string, HttpClient>();
// Add replaced named HTTP client
public void AddHttpClient(string clientName, HttpClient client)
{
if (!HttpClients.TryAdd(clientName, client))
{
throw new InvalidOperationException(
$"HttpClient with name {clientName} is already added");
}
}
// Replaces implementation of standard IHttpClientFactory interface with
// custom one providing replaced HTTP clients from HttpClients dictionary
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureServices(services =>
services.AddSingleton<IHttpClientFactory>(
new CustomHttpClientFactory(HttpClients)));
}
}
最后,CustomHttpClientFactory 类是必需的:
// Implements IHttpClientFactory by providing named HTTP clients
// directly from specified dictionary
internal class CustomHttpClientFactory : IHttpClientFactory
{
// Takes dictionary storing named HTTP clients in constructor
public CustomHttpClientFactory(
IReadOnlyDictionary<string, HttpClient> httpClients)
{
HttpClients = httpClients;
}
private IReadOnlyDictionary<string, HttpClient> HttpClients { get; }
// Provides named HTTP client from dictionary
public HttpClient CreateClient(string name) =>
HttpClients.GetValueOrDefault(name)
?? throw new InvalidOperationException(
$"HTTP client is not found for client with name {name}");
}
您可以在这里找到完整的示例代码:https://github.com/GennadyGS/AspNetCoreIntegrationTesting
这种方法的优点是:
- 能够测试服务之间的交互;
- 无需模拟服务的内部结构,以便您可以将它们视为黑匣子;
- 测试对于任何重构(包括通信协议的更改)都是稳定的;
- 测试速度快、自包含,不需要任何先决条件并提供可预测的结果。
这种方法的主要缺点是在现实世界的场景中参与服务(例如不同主要版本的 EFCore)可能会发生冲突的依赖关系,因为在测试中使用的所有服务都在单个进程中运行.
这种问题有几种缓解方法。其中之一是将模块化方法应用于服务的实现并根据配置文件在运行时加载模块。这可能允许在测试中替换配置文件,从加载中排除几个模块并用更简单的模拟替换丢失的服务。您可以在上面示例存储库的“模块化”分支中找到应用这种方法的示例。