Alba Documentation
repository·master·Indexed 19 days ago
https://github.com/jasperfx/albaAlba is a specialized tooling library for performing integration testing against ASP.NET Core applications. It uses scenarios to exercise the full application stack in-memory via the built-in TestServer, providing a declarative syntax for configuring HttpContext and asserting responses. The library supports both traditional Startup.cs and modern WebApplicationBuilder (Minimal API) approaches, offering features like configuration overrides, lifecycle hooks, and a TimeProviderOverride extension for deterministic time testing.
What's inside Alba
- Alba is a tooling library designed to improve integration testing against ASP.NET Core applications. It provides mechanisms to facilitate testing the full stack of an ASP.NET Core application more effectively.
What is Alba?
masterAlba is a class library designed for authoring integration tests against ASP.NET Core HTTP endpoints. It works alongside unit testing tools like xUnit.Net or NUnit.
Instead of manually managing
TestServerandHttpClient, Alba uses scenarios to exercise your full ASP.NET Core application in-memory using the built-in ASP.NET CoreTestServer. This allows for declarative, highly readable integration tests that serve as living technical documentation.Understand the AlbaHost interface and Scenario testing
masterAlbaHostextends the standard .NET CoreIHostinterface with additional capabilities specifically for testing.- Scenario Testing: The primary way to interact with the host is through the
Scenario()method, which allows you to define and execute Alba scenarios. - Accessing the Server: If you need to access the underlying
TestServerdirectly, you can use theIAlbaHost.Serverproperty. - Content Root Path: When using
AlbaHost.ForStartup<T>(), Alba attempts to guess the content root path based on the assembly name containing theStartupclass. If this guess is incorrect, you may need to override it manually.
- Scenario Testing: The primary way to interact with the host is through the
Use TimeProviderOverride for Time-travel Testing
masterAlba provides a
TimeProviderOverrideextension for time-travel testing. It uses aFakeTimeProvider-based approach to replace the application'sTimeProviderregistration across all bootstrapping styles.You pass the extension to
AlbaHost.For(...), and then drive time within your tests usingAdvance(...)andSetUtcNow(...).How SSE testing works in Alba
masterAlba provides two distinct mental models for testing Server-Sent Events (SSE) based on the stream's lifecycle:
Finite Streams (Buffered): Used when the endpoint is guaranteed to complete. You use the standard
ScenarioAPI. The entire response is buffered in memory, and you parse it usingReadAsServerSentEvents()after the request is finished. This is best for testing specific sequences of events that result in a closed connection.Live Streams (Streaming): Used for infinite streams or when you need to react to events in real-time. You use the
StreamServerSentEventsAPI. This avoids buffering the entire body, allowing you to iterate over events usingawait foreachas they are flushed by the server. This is essential for testing long-running connections or verifying that an endpoint responds to cancellation (viaRequestAborted).
How OpenTelemetry tracing works in Alba
masterAlba supports OpenTelemetry tracing withinScenariocalls. This allows you to perform distributed tracing within your CI pipeline, which can help identify the causes of broken, flaky, or slow-performing tests. Because Alba uses standard OpenTelemetry, it is compatible with any OpenTelemetry-compliant integration.How the Alba extension model works
masterAlba uses an extension model via the
IAlbaExtensioninterface to allow users to modify the application under test before it starts or perform actions after it has started.An extension can implement two primary lifecycle methods:
Configure(IAlbaHostBuilder builder): Runs before the application starts. It provides anIAlbaHostBuilderto add or replace services (ConfigureServices) or add configuration sources (ConfigureConfiguration). This works regardless of whether you are usingIHostBuilder,WebApplicationBuilder, orWebApplicationFactory.Start(IAlbaHost host): Runs after the application has started and the DI container is available. This is useful for registering setup or teardown actions.
Extensions are passed as an optional array to
AlbaHost.For<T>().// Example of passing an extension to AlbaHost var securityStub = new AuthenticationStub().With("foo", "bar"); var host = await AlbaHost.For<WebAppSecuredWithJwt.Program>(securityStub);Customize the system for testing
masterYou can override application configuration, environment settings, or service registrations during the
AlbaHostinitialization. This is useful for injecting mocks or stubs.Configuration Overrides
Use the configuration delegate in
AlbaHost.For<T>to callUseEnvironmentorConfigureServices.Lifecycle Hooks
Alba provides
BeforeEachandAfterEachhooks to perform data setup or cleanup around every scenario executed by the host.var stubbedWebService = new StubbedWebService(); await using var host = await AlbaHost.For<global::Program>(x => { // override the environment x.UseEnvironment("Testing"); // override service registrations x.ConfigureServices(s => { s.AddSingleton<IExternalWebService>(stubbedWebService); s.PostConfigure<MvcNewtonsoftJsonOptions>(o => o.SerializerSettings.TypeNameHandling = TypeNameHandling.All); }); }); host.BeforeEach(httpContext => { // do some data setup or clean up before every single test }) .AfterEach(httpContext => { // do any kind of cleanup after each scenario completes });Update IdentityModel usage to Duende.IdentityModel
masterAlba's OpenID Connect extensions now use theDuende.IdentityModelpackage. If you overrideFetchTokenor consume token types likeTokenResponseorDiscoveryDocumentResponse, update your using directives fromusing IdentityModel.Client;tousing Duende.IdentityModel.Client;.Run Alba documentation locally
masterIf you wish to run the Alba documentation website on your local machine, you must have a recent installation of NPM. The documentation is built using VitePress.
Use the following commands depending on your operating system:
- Windows:
build docs - Linux or OSX:
./build.sh docs
# On Windows build docs # On Linux or OSX ./build.sh docs- Windows:
Use xUnit Class Fixtures to share AlbaHost
masterTo improve performance in larger test suites, use xUnit's
IClassFixture<T>to share a singleAlbaHostinstance across all tests within a single test class.- Create a fixture class implementing
IAsyncLifetimeto manage theAlbaHostlifecycle. - Inject the fixture into your test class via the constructor.
// 1. Define the fixture public class WebAppFixture : IAsyncLifetime { public IAlbaHost AlbaHost = null!; public async ValueTask InitializeAsync() { AlbaHost = await Alba.AlbaHost.For<WebApp.Program>(builder => { // Configure all the things }); } public async ValueTask DisposeAsync() { await AlbaHost.DisposeAsync(); } } // 2. Use the fixture in a test class public class ContractTestWithAlba : IClassFixture<WebAppFixture> { public ContractTestWithAlba(WebAppFixture app) { _host = app.AlbaHost; } private readonly IAlbaHost _host; }- Create a fixture class implementing
Test live Server-Sent Event (SSE) streams
masterFor endpoints that stream indefinitely or require observing events mid-stream, use
StreamServerSentEventsinstead ofScenario. This method returns as soon as response headers arrive and does not buffer the response body.Key Behaviors:
- Consumption: Use
stream.ReadEvents(token)orstream.ReadEvents<T>(token)to yield events as the application writes them. The stream can only be consumed once. - Cancellation: Disposing the
SseStreamResultaborts the in-flight request and triggers the endpoint'sHttpContext.RequestAbortedtoken. - Lifecycle:
BeforeEach/BeforeEachAsyncand security extensions (likeJwtSecurityStuborWithClaim) work normally. However,AfterEach/AfterEachAsyncactions run with anullHttpContextupon disposal. - Limitations: Response assertions like
ContentShouldContainor header assertions are not supported and will be rejected. You must assert on the streamed events themselves. The response must have thetext/event-streamcontent type.
public async Task stream_live_events(IAlbaHost host) { using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); // Returns as soon as the response headers arrive; // the response body is never buffered await using var stream = await host.StreamServerSentEvents(x => { x.Get.Url("/sse/infinite"); }, timeout.Token); // Events are yielded as the application writes them await foreach (var item in stream.ReadEvents(timeout.Token)) { if (item.Data == "done") break; } // Disposing the stream aborts the request, cancelling the // endpoint's HttpContext.RequestAborted token }- Consumption: Use