Fork me on GitHub

Bootstrapping and Configuration


Alba uses its SystemUnderTest class to manage the lifecycle of an ASP.Net Core application. The easiest way to use this class is to directly use the Startup class for your application like so:


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

This is the bootstrapping equivalent in ASP.Net Core of using WebHost.CreateDefaultBuilder().UseStartup<T>().

Running a Scenario

Once you have a SystemUnderTest 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()
{
    using (var system = SystemUnderTest.ForStartup<Startup>())
    {
        // This runs an HTTP request and makes an assertion
        // about the expected content of the response
        await system.Scenario(_ =>
        {
            _.Get.Url("/");
            _.ContentShouldBe("Hello, World!");
            _.StatusCodeShouldBeOk();
        });
    }
}

The Scenario() method has this signature:


public static async Task<IScenarioResult> Scenario(
        this ISystemUnderTest system,
        Action<Scenario> configure)

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, and the response contains the raw HttpContext and a helper object to work with the response body:


public interface IScenarioResult
{
    HttpResponseBody ResponseBody { get; }

    HttpContext Context { get; }
}

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 = SystemUnderTest.ForStartup<Startup>())
    {
        var response = await system.Scenario(_ =>
        {
            _.Get.Url("/");
            _.StatusCodeShouldBeOk();
        });

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

        // or you can go straight at the HttpContext
        // The ReadAllText() extension method is from Baseline


        var body = response.Context.Response.Body;
        body.Position = 0; // need to rewind it because we read it above
        body.ReadAllText().ShouldBe("Hello, World!");
    }
}

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

Bootstrapping without Startup

It's a little bit uglier, but you can bypass having a Startup code and work directly against the ASP.Net Core IWebHostBuilder interface like this:


var system = SystemUnderTest.For(_ =>
{
    _.Configure(app =>
    {
        app.Run(c =>
        {
            return c.Response.WriteAsync("Hello, World!");
        });
    });
});

// or pass an IWebHostBuilder into the constructor function
// of SystemUnderTest

var builder = WebHost
    .CreateDefaultBuilder()
    .UseStartup<Startup>()
    .ConfigureServices(services =>
    {
        // override any service registrations you need,
        // like maybe using stubs for problematic dependencies
    });

var system2 = new SystemUnderTest(builder);

Overriding the Content Root Path

If you use Alba's SystemUnderTest.UseStartup<T>() helper, Alba can guess at what the content root path of the application can be by using the name of the assembly that contains your T class. If that guessing isn't right, you can explicitly tell Alba what the content root path should be to search for content with either of these options:



// Alba has a helper for overriding the root path
var system = SystemUnderTest
    .ForStartup<Startup>(rootPath:"c:\\path_to_your_actual_application");

// or do it with idiomatic ASP.Net Core

var builder = WebHost.CreateDefaultBuilder()
    .UseStartup<Startup>()
    .UseContentRoot("c:\\path_to_your_actual_application");

var system2 = new SystemUnderTest(builder);

On a related note, Alba does not set any default value for the EnvironmentName property. That can be overridden through IWebHostBuilder.UseEnvironment() as normal.

Hosting and Other Configuration

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 = WebHost.CreateDefaultBuilder()
    .UseStartup<Startup>()
    
    // override the environment if you need to
    .UseEnvironment("Testing")
    
    // override service registrations if you need to
    .ConfigureServices(s =>
    {
        s.AddSingleton<IExternalWebService>(stubbedWebService);
    });

// Create the SystemUnderTest, and alternatively override what Alba
// thinks is the main application assembly
// THIS IS IMPORTANT FOR MVC CONTROLLER DISCOVERY
var system = new SystemUnderTest(builder, applicationAssembly:typeof(Startup).Assembly);

system.BeforeEach(httpContext =>
{
    // do some data setup or clean up before every single test
});

system.AfterEach(httpContext =>
{
    // do any kind of cleanup after each scenario completes
});

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 SystemUnderTest with SystemUnderTest.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.

Best Practices

Spinning up your ASP.Net Core system can become slow as the application grows, so you probably want to share the instance of SystemUnderTest between unit tests to avoid the cost of constantly bootstrapping and tearing down the underlying application. As an example, if you're an xUnit user, you can take advantage of their shared context support to manage the lifetime of your SystemUnderTest.