Alba Documentation

repository·master·Indexed 19 days ago

https://github.com/jasperfx/alba

Alba 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.

Tokens
19.4K
Snippets
57
Records
67
Agent score
66%

What's inside Alba

  1. Overview of Alba

    master
    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.
  2. What is Alba?

    master

    Alba 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 TestServer and HttpClient, Alba uses scenarios to exercise your full ASP.NET Core application in-memory using the built-in ASP.NET Core TestServer. This allows for declarative, highly readable integration tests that serve as living technical documentation.

  3. Understand the AlbaHost interface and Scenario testing

    master

    AlbaHost extends the standard .NET Core IHost interface 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 TestServer directly, you can use the IAlbaHost.Server property.
    • Content Root Path: When using AlbaHost.ForStartup<T>(), Alba attempts to guess the content root path based on the assembly name containing the Startup class. If this guess is incorrect, you may need to override it manually.
  4. Use TimeProviderOverride for Time-travel Testing

    master

    Alba provides a TimeProviderOverride extension for time-travel testing. It uses a FakeTimeProvider-based approach to replace the application's TimeProvider registration across all bootstrapping styles.

    You pass the extension to AlbaHost.For(...), and then drive time within your tests using Advance(...) and SetUtcNow(...).

  5. How SSE testing works in Alba

    master

    Alba provides two distinct mental models for testing Server-Sent Events (SSE) based on the stream's lifecycle:

    1. Finite Streams (Buffered): Used when the endpoint is guaranteed to complete. You use the standard Scenario API. The entire response is buffered in memory, and you parse it using ReadAsServerSentEvents() after the request is finished. This is best for testing specific sequences of events that result in a closed connection.

    2. Live Streams (Streaming): Used for infinite streams or when you need to react to events in real-time. You use the StreamServerSentEvents API. This avoids buffering the entire body, allowing you to iterate over events using await foreach as they are flushed by the server. This is essential for testing long-running connections or verifying that an endpoint responds to cancellation (via RequestAborted).

  6. How OpenTelemetry tracing works in Alba

    master
    Alba supports OpenTelemetry tracing within Scenario calls. 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.
  7. How the Alba extension model works

    master

    Alba uses an extension model via the IAlbaExtension interface 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:

    1. Configure(IAlbaHostBuilder builder): Runs before the application starts. It provides an IAlbaHostBuilder to add or replace services (ConfigureServices) or add configuration sources (ConfigureConfiguration). This works regardless of whether you are using IHostBuilder, WebApplicationBuilder, or WebApplicationFactory.
    2. 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);
  8. Customize the system for testing

    master

    You can override application configuration, environment settings, or service registrations during the AlbaHost initialization. This is useful for injecting mocks or stubs.

    Configuration Overrides

    Use the configuration delegate in AlbaHost.For<T> to call UseEnvironment or ConfigureServices.

    Lifecycle Hooks

    Alba provides BeforeEach and AfterEach hooks 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
        });
  9. Update IdentityModel usage to Duende.IdentityModel

    master
    Alba's OpenID Connect extensions now use the Duende.IdentityModel package. If you override FetchToken or consume token types like TokenResponse or DiscoveryDocumentResponse, update your using directives from using IdentityModel.Client; to using Duende.IdentityModel.Client;.
  10. Run Alba documentation locally

    master

    If 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
  11. Use xUnit Class Fixtures to share AlbaHost

    master

    To improve performance in larger test suites, use xUnit's IClassFixture<T> to share a single AlbaHost instance across all tests within a single test class.

    1. Create a fixture class implementing IAsyncLifetime to manage the AlbaHost lifecycle.
    2. 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;
    }
  12. Test live Server-Sent Event (SSE) streams

    master

    For endpoints that stream indefinitely or require observing events mid-stream, use StreamServerSentEvents instead of Scenario. This method returns as soon as response headers arrive and does not buffer the response body.

    Key Behaviors:

    • Consumption: Use stream.ReadEvents(token) or stream.ReadEvents<T>(token) to yield events as the application writes them. The stream can only be consumed once.
    • Cancellation: Disposing the SseStreamResult aborts the in-flight request and triggers the endpoint's HttpContext.RequestAborted token.
    • Lifecycle: BeforeEach/BeforeEachAsync and security extensions (like JwtSecurityStub or WithClaim) work normally. However, AfterEach/AfterEachAsync actions run with a null HttpContext upon disposal.
    • Limitations: Response assertions like ContentShouldContain or header assertions are not supported and will be rejected. You must assert on the streamed events themselves. The response must have the text/event-stream content 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
    }