Specification style unit testing with xUnit.net

Chris Rodgers · 10 June, 2019

I have been using Machine Specifications off and on for a few years now, but have recently come up against an unsupported use case when trying to unit test an asynchronous codebase. We started writing some unit tests using xUnit.net, which has fantastic support for asynchronous unit tests, simply by implementing the IAsyncLifetime interface they provide, but I found myself missing specification style tests.

The problems I was seeing with our regular unit tests were that we were asserting on multiple statuses in a single unit test, or duplicating a lot of code in order to have single assertions per test. I wanted the base of both worlds, so I set about rewriting them in a specification style.

The first stage was to create a very simple base class, which implements the IAsyncLifetime interface, and provides Arrange, Act, and Cleanup methods to override.

public abstract class Specification : IAsyncLifetime
{
    protected virtual Task Arrange() => Task.CompletedTask;

    protected abstract Task Act();

    protected virtual Task Cleanup() => Task.CompletedTask;

    async Task IAsyncLifetime.InitializeAsync()
    {
        await Arrange();
        await Act();
    }

    async Task IAsyncLifetime.DisposeAsync()
    {
        await Cleanup();
    }
}

I can then write my specifications in a hierarchy, adding common functionality and setup code, then inheriting to specialise further. For example, for the fictional SqlFooStore below;

public interface FooStore
{
    Task<InsertResult> Insert(Foo foo);

    Task<UpdateResult> Update(Foo foo);
}

public class SqlFooStore : FooStore
{
    private readonly FooDbContext dbContext;

    public SqlFooStore(FooDbContext dbContext)
    {
        this.dbContext = dbContext;
    }

    public async Task<InsertResult> Insert(Foo foo)
    {
        ...
    }

    public async Task<UpdateResult> Update(Foo foo)
    {
        ...
    }
}

I could have an abstract base class for all SqlFooStore specifications;

[Category("SQL"), Category("FooStore")]
public abstract class SqlFooStoreSpecification : Specification
{
    protected Fixture Fixture { get; set; }
    protected FooDbContext DbContext { get; set; }
    protected SqlFooStore Subject { get; set; }

    protected override async Task Arrange()
    {
        await base.Arrange();
        Fixture = new Fixture(/* configuration */);
        var connection = new SqliteConnection("Data Source=:memory:;");
        connection.Open();
        var options = new DbContextOptionsBuilder<FooDbContext>()
            .UseSqlite(connection)
            .Options;
        DbContext = new FooDbContext(options);
        await DbContext.Database.EnsureCreatedAsync();
        Store = new SqlFooStore(DbContext);
    }
}

Note: in this example I have added AutoFixture to the base class. This is an awesome library, and I highly recommend it. I have also configured SQLite as the Entity Framework Core provider, as it provides a fully relational in-memory database.

I could then have seperate abstract base classes for my Insert method specifications and my Update method specifications.

[Category("SqlFooStore.Insert")]
public abstract class InsertFooSpecification : SqlFooStoreSpecification
{
    protected CreateResult Result { get; set; }
    protected Foo Foo { get; set; }

    protected override async Task Arrange()
    {
        await base.Arrange();
        Foo = Fixture
            .Build<Foo>()
            .Create();
    }

    protected override async Task Act() =>
        Result = await Subject.Insert(Foo);
}

[Category("SqlFooStore.Update")]
public abstract class UpdateFooSpecification : SqlFooStoreSpecification
{
    protected CreateResult Result { get; set; }
    protected Foo Foo { get; set; }

    protected override async Task Arrange()
    {
        await base.Arrange();
        Foo = Fixture
            .Build<Foo>()
            .Create();
        await DbContext.Foos.AddAsync(Foo);
        await DbContext.SaveChangesAsync();
    }

    protected override async Task Act() =>
        Result = await Subject.Update(Foo);
}

I can then have classes for each scenario, with individual test methods for each assertion. I also keep all of my scenario classes nested inside a container class, to avoid clashes when scenarios have the same name.

public class InsertFooSpecifications
{
    public class When_the_foo_is_valid : InsertFooSpecification
    {
        [Fact]
        public void It_returns_a_success_result() =>
            Result.Should().BeSuccess();

        [Fact]
        public void It_assigns_an_id_to_the_foo() =>
            Result.Foo.Id.Should().NotEqual(0);

        [Fact]
        public void It_should_be_added_to_the_underlying_datastore() =>
            DbContext.Foos.Find(Result.Foo.Id).Should().NotBeNull();
    }

    public class When_the_foo_name_is_missing : InsertFooSpecification
    {
        protected override async Task Arrange()
        {
            await base.Arrange();
            Foo.Name = null;
        }

        [Fact]
        public void It_returns_a_failure_result() =>
            Result.Should().BeFailure();

        [Fact]
        public void It_should_not_assign_an_id_to_the_foo() =>
            Result.Foo.Id.Should().BeEqual(0);

        [Fact]
        public void It_should_not_be_added_to_the_underlying_datastore() =>
            DbContext.Foos.Find(Result.Foo.Id).Should().BeNull();
    }
}

Note: in this example I am using Fluent Assertions to decribe my expected results.

This allows me to have an assertion for unit test, while minimising code duplication. It does mean I have a rather tall inheritence tree for my unit tests but, you know what? That doesn’t bother me.

Twitter, Facebook