Skip to content

Integrating with NUnit ​

When using Alba within NUnit testing projects, you probably want to reuse the IAlbaHost across tests and test fixtures because AlbaHost is relatively expensive to create (it's bootstrapping your whole application more than Alba itself is slow). To do that with NUnit, you could track a single AlbaHost on a static class like this one:

cs
[SetUpFixture]
public class Application
{
    [OneTimeSetUp]
    public async Task Init()
    {
        Host = await AlbaHost.For<WebApp.Program>();
    }
        
    public static IAlbaHost Host { get; private set; }

    // Make sure that NUnit will shut down the AlbaHost when
    // all the projects are finished
    [OneTimeTearDown]
    public void Teardown()
    {
        Host.Dispose();
    }
}

snippet source | anchor

Any Alba specific setup, such as configuration overrides or extensions, is chained onto AlbaHost.For<T>() before it is awaited (see Configuring the Host Fluently).

Then reference the AlbaHost in tests like this sample:

cs
public class sample_integration_fixture
{
    [Test]
    public async Task happy_path()
    {
        await Application.Host.Scenario(_ =>
        {
            _.Get.Url("/fake/okay");
            _.StatusCodeShouldBeOk();
        });
    }
}

snippet source | anchor

Running tests in parallel with a host pool ​

Tests that change data can't safely share one host while they run in parallel. An AlbaHostPool lends each test a host of its own. Create the pool in a [SetUpFixture], and dispose it in its [OneTimeTearDown]:

cs
// Applies to the tests in this namespace
[SetUpFixture]
public class PooledApplication
{
    public static AlbaHostPool Pool { get; private set; }

    [OneTimeSetUp]
    public void CreatePool()
    {
        Pool = new AlbaHostPool(slot => AlbaHost.For<WebApp.Program>()
                // Each host gets its own database
                .WithConfiguration("ConnectionStrings:Db", $"Host=localhost;Database=app_{slot.Index}"),
            new AlbaHostPoolOptions
            {
                // Hosts start as tests need them, up to this many
                MaxHosts = 4
            });
    }

    // Runs after every test in the namespace has finished
    [OneTimeTearDown]
    public async Task DisposePool()
    {
        await Pool.DisposeAsync();
    }
}

snippet source | anchor

Then rent a host in [SetUp] and return it in [TearDown]. NUnit runs tests one at a time unless they are marked [Parallelizable], and it shares one fixture instance between a class's tests unless the class uses [FixtureLifeCycle(LifeCycle.InstancePerTestCase)]. Without that attribute, tests running in parallel would overwrite each other's lease:

cs
// A new instance for each test, so tests running in parallel each keep their own lease
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
[Parallelizable(ParallelScope.Children)]
public class pooled_math_tests
{
    private AlbaHostLease _host;

    [SetUp]
    public async Task RentHost()
    {
        // Name the test, because timeout messages would otherwise name this method
        _host = await PooledApplication.Pool.RentAsync(TestContext.CurrentContext.CancellationToken,
            caller: TestContext.CurrentContext.Test.Name);
    }

    // Resets the host and returns it to the pool
    [TearDown]
    public async Task ReturnHost()
    {
        if (_host != null) await _host.DisposeAsync();
    }

    [TestCase(1, 2, 3)]
    [TestCase(2, 3, 5)]
    [TestCase(10, 20, 30)]
    public async Task adds_numbers(int one, int two, int sum)
    {
        await _host.Scenario(x =>
        {
            x.Get.Url($"/math/add/{one}/{two}");
            x.JsonPath("$.answer").ShouldBe(sum);
        });
    }
}

snippet source | anchor