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?
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.
ASP.NET Core has an extremely useful integration testing framework, in the form of the NuGet package Microsoft.
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.
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.
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);
}
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With