Skip to content

GennadyGS/AspNetCoreIntegrationTesting

Repository files navigation

AspNetCoreIntegrationTesting

End‑to‑end (a.k.a. full stack) integration testing example for two communicating ASP.NET Core APIs hosted entirely in the test process.

This repository demonstrates how to:

  • Spin up multiple ASP.NET Core services side‑by‑side inside an xUnit test using WebApplicationFactory.
  • Exercise real HTTP calls between those services (no mocks, no in‑memory shortcuts) without deploying anything externally.
  • Replace a named HttpClient (configured in the application under test) with a test-provided HttpClient that targets another in‑process service instance.
  • Override IHttpClientFactory in a controlled way so the production registration (AddHttpClient / typed clients) stays untouched.

Scenario

There are two services:

  1. WebService (utility / downstream service) – exposes GET /weatherForecast returning 5 forecast items.
  2. WebApplication (master / upstream service) – exposes GET /weatherForecast which internally calls the downstream service twice and concatenates the results (expecting 10 items total).

In production, WebApplication configures a named HttpClient "WebService" with its base address coming from configuration key ServerUrl. During tests we do not want to hit a real deployed WebService. Instead we host an in‑process copy of WebService and transparently redirect WebApplication's outgoing HttpClient calls to it.


Key Concepts

1. WebApplicationFactory for each service

Two factory instances are created inside the test: one for WebApplication and one for WebService. Each yields an HttpClient pointed at the respective in‑memory server.

2. Custom IHttpClientFactory

CustomWebApplicationFactory overrides the DI registration for IHttpClientFactory with a CustomHttpClientFactory that serves pre-created named HttpClient instances from a concurrent dictionary.

// In test
var webAppFactory = new CustomWebApplicationFactory();
var webAppClient = webAppFactory.CreateClient();

var webServiceFactory = new WebApplicationFactory<WebService.Controllers.WeatherForecastController>();
var webServiceClient = webServiceFactory.CreateClient();

// Provide the downstream client under the name expected by the app
webAppFactory.AddHttpClient("WebService", webServiceClient);

Because we replace the entire IHttpClientFactory, the typed client (WeatherForecastClient) receives our injected downstream HttpClient instead of one built from production configuration (ServerUrl). No code changes to the application are required.

3. Typed Client

WeatherForecastClient is registered as a typed client for the named client "WebService" and simply performs two GET calls, concatenating responses.

4. Deterministic Assertions

The downstream service returns 5 items per call; the upstream endpoint must therefore yield 10. The test asserts both HTTP success and item count.


Test Flow

  1. Start WebApplication (under test).
  2. Start WebService (dependency) in the same process space via its own factory.
  3. Register the WebService HttpClient instance under the expected name ("WebService").
  4. Issue a GET to /weatherForecast on WebApplication.
  5. WebApplication internally performs two real HTTP calls to WebService.
  6. Response is validated (HTTP 200 + 10 forecast items).

Why Replace IHttpClientFactory?

Alternative approaches (e.g., editing configuration, using DelegatingHandlers, or mocking the typed client) either:

  • Couple the test to implementation details (base addresses, configuration keys), or
  • Bypass real HTTP behavior (losing coverage of serialization, middleware, etc.).

Replacing IHttpClientFactory lets the original AddHttpClient & typed client registration stay intact, while seamlessly redirecting network calls inside the same process with minimal plumbing.


Running the Example

Prerequisites: .NET 9 SDK.

Build:

dotnet build

Run tests:

dotnet test

Optional: run services individually (in two terminals) if you want to browse Swagger UIs.

cd src/WebService && dotnet run
cd src/WebApplication && dotnet run

Then open the Swagger endpoints displayed in the console output.


Notes

  • ServerUrl in appsettings.json is only used in production hosting; tests bypass it by replacing the factory.
  • The approach keeps tests black‑box from the perspective of HTTP boundaries while remaining fully in‑memory (no real sockets bound externally).
  • Suitable for verifying serialization, filters, middleware, routing, DI wiring, and inter‑service orchestration logic together.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages