AspNetCore集成测试多个WebApplicationFactory实例?

时间:2019-03-01 11:58:03

标签: c# unit-testing asp.net-core integration-testing asp.net-core-2.2

有人知道是否可以在同一单元测试中托管WebApplicationFactory<TStartop>()的多个实例吗?

我已经尝试过并且似乎无法解决这一问题。

_client = WebHost<Startup>.GetFactory().CreateClient();
var baseUri = PathString.FromUriComponent(_client.BaseAddress);
_url = baseUri.Value;

_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", "Y2E890F4-E9AE-468D-8294-6164C59B099Y");

WebHost只是一个帮助器类,它使我可以轻松地在一行中建立工厂,然后建立一个客户。

在幕后,所有要做的就是这样:

new WebApplicationFactory<TStartup>(),还有其他一些事情。

如果我能站在另一台Web服务器的另一个实例上来测试服务器到服务器的功能,那将很好。

有人知道这是否可行吗?

3 个答案:

答案 0 :(得分:2)

与接受的答案相反,实际上,使用两个WebApplicationFactory实例测试服务器到服务器的功能非常容易:

public class OrderAPIFactory : WebApplicationFactory<Order>
{
    public OrderAPIFactory() { ... }
    protected override void ConfigureWebHost(IWebHostBuilder builder) { ... }
}

public class BasketAPIFactory : WebApplicationFactory<BasketStartup>
{
    public BasketAPIFactory() { ... }
    protected override void ConfigureWebHost(IWebHostBuilder builder) { ... }
}

然后,您可以如下实例化自定义工厂:

[Fact] 
public async Task TestName()
{
    var orderFactory = new OrderAPIFactory();
    var basketFactory = new BasketAPIFactory();

    var orderHttpClient = orderFactory.CreateClient();
    var basketHttpClient = basketFactory.CreateClient();

    // you can hit eg an endpoint on either side that triggers server-to-server communication
    var orderResponse = await orderHttpClient.GetAsync("api/orders");
    var basketResponse = await basketHttpClient.GetAsync("api/basket");
}

我也不同意公认的答案,因为答案肯定是设计不好:它有用例。我公司的微服务基础结构依赖于data duplication across microservices并使用async messaging queue with integration events来确保数据一致性。不用说,消息传递功能起着核心作用,需要进行适当的测试。在这种情况下,此处描述的测试设置非常有用。例如,它使我们能够彻底测试发布消息时已关闭的服务如何处理消息:

[Fact] 
public async Task DataConsistencyEvents_DependentServiceIsDown_SynchronisesDataWhenUp()
{
    var orderFactory = new OrderAPIFactory();
    var orderHttpClient = orderFactory.CreateClient();

    // a new order is created which leads to a data consistency event being published,
    // which is to be consumed by the BasketAPI service 
    var order = new Order { ... };
    await orderHttpClient.PostAsync("api/orders", order);

    // we only instantiate the BasketAPI service after the creation of the order
    // to mimic downtime. If all goes well, it will still receive the 
    // message that was delivered to its queue and data consistency is preserved
    var basketFactory = new BasketAPIFactory();
    var basketHttpClient = orderFactory.CreateClient();

    // get the basket with all ordered items included from BasketAPI
    var basketResponse = await basketHttpClient.GetAsync("api/baskets?include=orders");
    // check if the new order is contained in the payload of BasketAPI
    AssertContainsNewOrder(basketResponse, order); 
}

答案 1 :(得分:1)

可以在单个集成测试中托管WebApplicationFactory的多个通信实例。

假设我们有一个名为WebApplication的主服务,它依赖于名为WebService的实用程序服务,该服务使用名为HttpClient且名称为“ WebService”的应用程序。

这是集成测试的示例:

[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的不同主要版本)在现实世界场景中可能存在冲突的依赖关系,因为测试中使用的所有服务都在单个进程中运行。 有一些缓解此问题的方法。其中之一是将模块化方法应用于服务的实现,并根据配置文件在运行时加载模块。这可能允许替换测试中的配置文件,从加载中排除几个模块,并使用更简单的模拟替换丢失的服务。您可以在上面的示例存储库的“模块”分支中找到采用这种方法的示例。

答案 2 :(得分:0)

不。这是不可能的。 WebApplicationFactory依靠xUnit的IClassFixture,它必须在课程级别应用,这意味着您只能对苹果一口气。 WebApplicationFactory本身可以针对每个测试进行自定义,可以满足大多数需要“不同”的用例的情况,但是这并不能帮助您同时需要两个完全独立的活动测试服务器。

但是,也就是说,您首先要的是不良的测试设计。测试的全部目的是消除变量,以便您实际上可以确保SUT的一部分正常工作。即使在集成测试环境中,您仍然只是在查看应用程序各部分之间的特定交互。有两台测试服务器相互配合,可以有效地将变量相乘,从而无法保证任何一方都能正常工作。