Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AspNetCore Integration Testing Multiple WebApplicationFactory Instances?

Does any one know if it is possible to host multiple instances of WebApplicationFactory<TStartop>() in the same unit test?

I have tried and can't seem to get anywhere with this one issue.

i.e

_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 is just a helper class that allows me to build factory and then a client easily in one line.

Under the covers all it does is this:

new WebApplicationFactory<TStartup>() but a few other things too.

It would be nice if i could stand up another instace of a different web server to test server to server functionality.

Does anyone know if this is possible or not?

like image 693
IbrarMumtaz Avatar asked Mar 01 '19 11:03

IbrarMumtaz


People also ask

What is WebApplicationFactory?

WebApplicationFactory provides multiple ways to customise your application in integration tests, but at it's core, it provides a way to run your application's Host instance in memory.

Can Nunit be used for integration testing?

ASP.NET Core has an extremely useful integration testing framework, in the form of the NuGet package Microsoft.

Which package component can be added for integration testing of the .NET core API applications?

The test web host (TestServer) is available in a NuGet component as Microsoft. AspNetCore. TestHost. It can be added to integration test projects and used to host ASP.NET Core applications.

What are the types of integration testing?

Some different types of integration testing are big-bang, mixed (sandwich), risky-hardest, top-down, and bottom-up. Other Integration Patterns are: collaboration integration, backbone integration, layer integration, client-server integration, distributed services integration and high-frequency integration.


2 Answers

Contrary to what the accepted answer states, it is actually pretty easy to test server to server functionality using two WebApplicationFactory instances:

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) { ... }
}

Then you can instantiate the custom factories as follows:

[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");
}

I also disagree with the accepted answer about it necessarily being bad design: it has its use-cases. My company has a microservices infrastructure which relies on data duplication across microservices and uses an async messaging queue with integration events to ensure data consistency. Needless to say that messaging functionality plays a central role and needs to be tested properly. A test setup as described here is pretty useful in this situation. For example it allows us to thoroughly test how messages are being dealt with by a service that was down at the moment those messages were published:

[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); 
}
like image 169
Maurits Moeys Avatar answered Nov 03 '22 04:11

Maurits Moeys


It is possible to host multiple communicating instances of WebApplicationFactory in single integration test.

Let's say we have master service named WebApplication, which depends on utility service named WebService using named HttpClient with name "WebService".

Here is example of integration test:

[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());
}

This code requires CustomWebApplicationFactory class to be implemented:

// 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)));
    }
}

And finally, CustomHttpClientFactory class is required:

// 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}");
}

The complete code of example you may find here: https://github.com/GennadyGS/AspNetCoreIntegrationTesting

The pros of such approach are:

  • ability to test interactions between the services;
  • no need to mock internals of services so that you can consider them as black boxes;
  • tests are stable to any refactorings including changes in communication protocol;
  • tests are fast, self-contained, do not require any prerequisites and give predictable results.

The main cons of such approach is possible conflicting dependencies of participating services (e.g. different major versions of EFCore) in real world scenarios due to the fact that all services using in test are running in single process. There are several mitigations of such problem. One of them is to apply modular approach to services' implementations and load modules in runtime according to configuration file. This may allow to replace configuration file in tests, exclude several modules from loading and replace missing services with simpler mocks. The example of applying such approach you may find in branch "Modular" of the example repository above.

like image 22
Gennadii Saltyshchak Avatar answered Nov 03 '22 05:11

Gennadii Saltyshchak