Bootstrapping and Configuration

Initializing AlbaHost

To get started with Alba, just add a Nuget reference to the Alba library to your testing project. In the following sections I'll show you how to bootstrap your ASP.Net Core system with Alba and start authoring specifications with the IAlbaHost abstraction.

To bootstrap and connect any ASP.Net Core application to Alba, create a AlbaHost using the definition of your IHostBuilder as shown below:

[Fact]
public async Task build_host_from_Program()    
{
    // Bootstrap your application just as your real application does
    var hostBuilder = Program.CreateHostBuilder(Array.Empty<string>());

    await using var host = new AlbaHost(hostBuilder);

    // Just as a sample, I'll run a scenario against
    // a "hello, world" application's root url
    await host.Scenario(s =>
    {
        s.Get.Url("/");
        s.ContentShouldBe("Hello, World!");
    });
}

snippet source | anchor

TIP

There are both synchronous and asynchronous methods to bootstrap an AlbaHost. Depending on your test harness, I recommend using the asynchronous version whenever applicable.

Or alternatively, you can use one of the Alba extension methods off of IHostBuilder to start an AlbaHost object in a fluent interface style:

[Fact]
public async Task fluent_interface_bootstrapping()    
{
    await using var host = await Program
        .CreateHostBuilder(Array.Empty<string>())
        .StartAlbaAsync();

    // Just as a sample, I'll run a scenario against
    // a "hello, world" application's root url
    await host.Scenario(s =>
    {
        s.Get.Url("/");
        s.ContentShouldBe("Hello, World!");
    });
}

snippet source | anchor

The AlbaHost is an extension of the standard .Net Core IHost interface with a few additions for testing support. While you can always access the underlying TestServer through the IAlbaHost.Server property, you're mostly going to be using the Scenario() method to write Alba "Scenario" tests.

TIP

To make the samples in this page easier to follow, I'm bootstrapping the IAlbaHost within each test. In real usage, bootstrapping your application is expensive and you will probably want to reuse the IAlbaHost between tests. See the integrations with NUnit and xUnit.Net for examples.

Running a Scenario

Once you have a AlbaHost object, you're ready to execute Scenario's through your system inside of tests. Below is a scenario for the "hello, world" application:

[Fact]
public async Task should_say_hello_world()
{
    await using var host = await Program
        .CreateHostBuilder(Array.Empty<string>())
        
        // This extension method is just a shorter version
        // of new AlbaHost(builder)
        .StartAlbaAsync();
    
    // This runs an HTTP request and makes an assertion
    // about the expected content of the response
    await host.Scenario(_ =>
    {
        _.Get.Url("/");
        _.ContentShouldBe("Hello, World!");
        _.StatusCodeShouldBeOk();
    });
}

snippet source | anchor

The single Action<Scenario> argument should completely configure the ASP.Net HttpContext for the request and apply any of the declarative response assertions. The actual HTTP request happens inside of the Scenario() method. The response contains the raw HttpContext and several helper methods to help you parse or read information from the response body:

public interface IScenarioResult
{
    /// <summary>
    ///     Helpers to interrogate or read the HttpResponse.Body
    ///     of the request
    /// </summary>
    [Obsolete("Use the methods directly on IScenarioResult instead")]
    HttpResponseBody ResponseBody { get; }

    /// <summary>
    ///     The raw HttpContext used during the scenario
    /// </summary>
    HttpContext Context { get; }

    /// <summary>
    /// Read the contents of the HttpResponse.Body as text
    /// </summary>
    /// <returns></returns>
    string ReadAsText();

    /// <summary>
    /// Read the contents of the HttpResponse.Body into an XmlDocument object
    /// </summary>
    /// <returns></returns>
    XmlDocument? ReadAsXml();

    /// <summary>
    /// Deserialize the contents of the HttpResponse.Body into an object
    /// of type T using the built in XmlSerializer
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    T? ReadAsXml<T>() where T : class;

    /// <summary>
    /// Deserialize the contents of the HttpResponse.Body into an object
    /// of type T using the configured Json serializer
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    T? ReadAsJson<T>();

    T? Read<T>(string contentType);
}

snippet source | anchor

TIP

Alba v5 makes the old HttpResponseBody obsolete. It's still there, but the same functionality is readily available on IScenarioResult.

If the existing Scenario assertions aren't enough to verify your test case, you can work directly against the raw response:

[Fact]
public async Task should_say_hello_world_with_raw_objects()
{
    using (var system = AlbaHost.ForStartup<Startup>())
    {
        var response = await system.Scenario(_ =>
        {
            _.Get.Url("/");
            _.StatusCodeShouldBeOk();
        });

        response.ReadAsText()
            .ShouldBe("Hello, World!");

        // or you can go straight at the HttpContext
        Stream responseStream = response.Context.Response.Body;
        // do assertions directly on the responseStream
    }
}

snippet source | anchor

Do note that Alba quietly "rewinds" the HttpContext.Response.Body stream so that you can more readily read and work with the contents.

Customizing the System for Testing

If you also want to run real HTTP requests through your system in a test harness, you have more opportunities to configure the underlying IWebHostBuilder like so:

var stubbedWebService = new StubbedWebService();

var builder = Host.CreateDefaultBuilder()
    .ConfigureWebHostDefaults(c => c.UseStartup<Startup>())

    // override the environment if you need to
    .UseEnvironment("Testing")

    // override service registrations or internal options if you need to
    .ConfigureServices(s =>
    {
        s.AddSingleton<IExternalWebService>(stubbedWebService);
        s.PostConfigure<MvcNewtonsoftJsonOptions>(o =>
            o.SerializerSettings.TypeNameHandling = TypeNameHandling.All);
    });

// Create the SystemUnderTest
var system = new AlbaHost(builder)
    .BeforeEach(httpContext =>
    {
        // do some data setup or clean up before every single test
    })
    .AfterEach(httpContext =>
    {
        // do any kind of cleanup after each scenario completes
    });

snippet source | anchor

A couple notes:

  • Alba does not do anything to set the hosting environment, but you can do that yourself against IWebHostBuilder
  • If you build a AlbaHost with AlbaHost.ForStartup<T>(), it will try to guess at the content root path by the name of assembly that holds the Startup class, but you may need to override that yourself.

My shop is also using Alba within Storyteller specifications where we use a mix of headless Alba Scenario's and full HTTP requests for testing.