Vertical Slice Test Fixtures for MediatR and ASP.NET Core

One of the nicest side effects of using MediatR is that my controllers become quite thin. Here’s a typical controller:

public class CourseController : Controller
{
    private readonly IMediator _mediator;

    public CourseController(IMediator mediator)
    {
        _mediator = mediator;
    }

    public async Task<IActionResult> Index(Index.Query query)
    {
        var model = await _mediator.SendAsync(query);

        return View(model);
    }

    public async Task<IActionResult> Details(Details.Query query)
    {
        var model = await _mediator.SendAsync(query);

        return View(model);
    }

    public ActionResult Create()
    {
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create(Create.Command command)
    {
        _mediator.Send(command);

        return this.RedirectToActionJson(nameof(Index));
    }

    public async Task<IActionResult> Edit(Edit.Query query)
    {
        var model = await _mediator.SendAsync(query);

        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(Edit.Command command)
    {
        await _mediator.SendAsync(command);

        return this.RedirectToActionJson(nameof(Index));
    }

    public async Task<IActionResult> Delete(Delete.Query query)
    {
        var model = await _mediator.SendAsync(query);

        return View(model);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Delete(Delete.Command command)
    {
        await _mediator.SendAsync(command);

        return this.RedirectToActionJson(nameof(Index));
    }
}

Unit testing this controller is a tad pointless – I’d only do it if the controller actions were doing something interesting. With MediatR combined with CQRS, my application is modeled as a series of requests and responses, where my requests either represent a command or a query. In an actual HTTP request, I wrap my request in a transaction using an action filter, so the request looks something like:

image

The bulk of the work happens in my handler, of course. Now, in my projects, we have a fairly strict rule that all handlers need a test. But what kind of test should it be? At the very least, we want a test that executes our handlers as they would be used in a normal application. Since my handlers don’t have any real knowledge of the UI, they’re simple DTO-in, DTO-out handlers, I don’t need to worry about UI dependencies like controllers, filters and whatnot.

I do, however, want to build a test that goes just under the UI layer to execute the end-to-end behavior of my system. These are known as “subcutaneous tests”, and provide me with the greatest confidence that one of my vertical slices does indeed work. It’s the first test I write for a feature, and the last test to pass.

I need to make sure that my test properly matches “real world” usage of my system, which means that I’ll execute a series of transactions, one for the setup/execute/verify steps of my test:

image

The final piece is allowing my test to easily run these kinds of tests over and over again. To do so, I’ll combine a few tools at my disposal to ensure my tests run in a repeatable and predictable fashion.

Building the fixture

The baseline for my tests is known as a “fixture”, and what I’ll be building is a known starting state for my tests. There are a number of different environments I can do this in, but the basic idea are:

  • Reset the database before each test using Respawn to provide a known database starting state
  • Provide a fixture class that represents the known application starting state

I’ll show how to do this with xUnit, but the Fixie example is just as easy. First, I’ll need a known starting state for my fixture:

public class ContainerFixture
{
    private static readonly Checkpoint _checkpoint;
    private static readonly IServiceProvider _rootContainer;
    private static readonly IConfigurationRoot _configuration;
    private static readonly IServiceScopeFactory _scopeFactory;

    static ContainerFixture()
    {
        var host = A.Fake<IHostingEnvironment>();

        A.CallTo(() => host.ContentRootPath).Returns(Directory.GetCurrentDirectory());

        var startup = new Startup(host);
        _configuration = startup.Configuration;
        var services = new ServiceCollection();
        startup.ConfigureServices(services);
        _rootContainer = services.BuildServiceProvider();
        _scopeFactory = _rootContainer.GetService<IServiceScopeFactory>();
        _checkpoint = new Checkpoint();
    }

I want to use the exact same startup configuration that I use in my actual application that I do in my tests. It’s important that my tests match as much as possible the runtime configuration of my system. Mismatches here can easily result in false positives in my tests. The only thing I have to fake out are my hosting environment. Unlike the integration testing available for ASP.NET Core, I won’t actually run a test server. I’m just running through the same configuration. I capture some of the output objects as fields, for ease of use later.

Next, on my fixture, I expose a method to reset the database (for later use):

public static void ResetCheckpoint()
{
    _checkpoint.Reset(_configuration["Data:DefaultConnection:ConnectionString"]);
}

I can now create an xUnit behavior to reset the database before every test:

public class ResetDatabaseAttribute : BeforeAfterTestAttribute
{
    public override void Before(MethodInfo methodUnderTest)
    {
        SliceFixture.ResetCheckpoint();
    }
}

With xUnit, I have to decorate every test with this attribute. With Fixie, I don’t. In any case, now that I have my fixture, I can inject it with an IClassFixture interface:

public class EditTests : IClassFixture<SliceFixture>
{
    private readonly SliceFixture _fixture;

    public EditTests(SliceFixture fixture)
    {
        _fixture = fixture;
    }

With my fixture created, and a way to inject it into my tests, I can now use it in my tests.

Building setup/execute/verify fixture seams

In each of these steps, I want to make sure that I execute each of them in a committed transaction. This ensures that my test, as much as possible, matches the real world usage my application. In an actual system, the user clicks around, executing a series of transactions. In the case of editing an entity, the user first looks at a screen of an existing entity, POSTs a form, and then is redirected to a new page where they see the results of their action. I want to mimic this sort of flow in my tests as well.

First, I need a way to execute something against a DbContext as part of a transaction. I’ve already exposed methods on my DbContext to make it easier to manage a transaction, so I just need a way to do this through my fixture. The other thing I need to worry about is that with the built-in DI container with ASP.NET Core, I need to create a scope for scoped dependencies. That’s why I captured out that scope factory earlier. With the scope factory, it’s trivial to create a nice method to execute a scoped action:

public async Task ExecuteScopeAsync(Func<IServiceProvider, Task> action)
{
    using (var scope = _scopeFactory.CreateScope())
    {
        var dbContext = scope.ServiceProvider.GetService<DirectoryContext>();

        try
        {
            dbContext.BeginTransaction();

            await action(scope.ServiceProvider);

            await dbContext.CommitTransactionAsync();
        }
        catch (Exception)
        {
            dbContext.RollbackTransaction();
            throw;
        }
    }
}

public Task ExecuteDbContextAsync(Func<DirectoryContext, Task> action)
{
    return ExecuteScopeAsync(sp => action(sp.GetService<SchoolContext>()));
}

The method takes function that accepts an IServiceProvider and returns a Task (so that your action can be async). For convenience sake, if you just need a DbContext, I also provide an overload that just works with that instance. With this in place, I can build out the setup portion of my test:

[Fact]
[ResetDatabase]
public async Task ShouldEditEmployee()
{
    var employee = new Employee
    {
        Email = "jane@jane.com",
        FirstName = "Jane",
        LastName = "Smith",
        Title = "Director",
        Office = Office.Austin,
        PhoneNumber = "512-555-4321",
        Username = "janesmith",
        HashedPassword = "1234567890"
    };

    await _fixture.ExecuteDbContextAsync(async dbContext =>
    {
        dbContext.Employees.Add(employee);
        await dbContext.SaveChangesAsync();
    });

I build out an entity, and in the context of a scope and transaction, save it out. I’m intentionally NOT reusing those scopes or DbContext objects across the different setup/execute/verify steps of my test, because that’s not what happens in my app! My actual application creates a distinct scope per operation, so I should do that too.

Next, for the execute step, this will involve sending a request to the Mediator. Again, as with my DbContext method, I’ll create a convenience method to make it easy to send a scoped request:

public async Task<TResponse> SendAsync<TResponse>(IAsyncRequest<TResponse> request)
{
    var response = default(TResponse);
    await ExecuteScopeAsync(async sp =>
    {
        var mediator = sp.GetService<IMediator>();

        response = await mediator.SendAsync(request);
    });
    return response;
}

Since my “mediator.SendAsync” executes inside of that scope, with a transaction, I can be confident that when the handler completes it’s pushed the results of that handler all the way down to the database. My test now can send a request fairly easily:

var command = new Edit.Command
{
    Id = employee.Id,
    Email = "jane@jane2.com",
    FirstName = "Jane2",
    LastName = "Smith2",
    Office = Office.Dallas,
    Title = "CEO",
    PhoneNumber = "512-555-9999"
};

await _fixture.SendAsync(command);

Finally, in my verify step, I can use the same scope-isolated ExecuteDbContextAsync method to start a new transaction to do my assertions against:

await _fixture.ExecuteDbContextAsync(async dbContext =>
{
    var found = await dbContext.Employees.FindAsync(employee.Id);

    found.Email.ShouldBe(command.Email);
    found.FirstName.ShouldBe(command.FirstName);
    found.LastName.ShouldBe(command.LastName);
    found.Office.ShouldBe(command.Office);
    found.Title.ShouldBe(command.Title);
    found.PhoneNumber.ShouldBe(command.PhoneNumber);
});

With the setup, execute, and verify steps each in their own isolated transaction and scope, I ensure that my vertical slice test matches as much as possible the flow of actual usage. And again, because I’m using MediatR, my test only knows how to send a request down and verify the result. There’s no coupling whatsoever of my test to the implementation details of the handler. It could use EF, NPoco, Dapper, sprocs, really anything.

Wrapping up

Most integration tests I see wrap the entire test in a transaction or scope of some sort. This can lead to pernicious false positives, as I’ve not made the full round trip that my application makes. With subcutaneous tests executing against vertical slices of my application, I’ve got the closest representation as possible without executing HTTP requests to how my application actually works.

One thing to note – the built-in DI container doesn’t allow you to alter the container after it’s built. With more robust containers like StructureMap, I’d along with that scope allow you to register mock/stub dependencies that only live for that scope (using the magic of nested containers).

If you want to see a full working example using Fixie, check out Contoso University Core.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASPNetCore, MediatR, Testing. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Dan

    Thank you for taking the time to share ContosoUniversity and walking us through it, this is so valuable to see ideas laid out in working code using a non-trivial example.

    I’m sold on MediatR and the features organizational style, that’s going in my toolbox of solutions.

  • malisancube

    Thanks for the Contoso University application. I wish we had more like that.

    Can we help in bug fixes?

    • jbogard

      Pull request away!

  • João Carlos Clementoni Silva

    Hi Jimmy!

    Thanks for sharing your experience.

    I wonder how you manage the arrange part of the test when you have to create an Entity that depends on other, lets say, 10 Entities through Foreign-Keys.

    I used yours test approach in a project with almost 130 tables, and the Integrated Tests testing Command/Queries handlers started get slow.
    How do you manage it?

    Thanks a lot!

    • jbogard

      For the large entities problem, you’ve got a few options. The xUnit test patterns book is a good one to have around, but the basic gist is you can have test object builders build out your objects with sensible defaults. Or, you can use a tool like AutoFixture to help.

      For the slowness issue – you’ll still need to drive to build unit tests. But now your unit tests are only going through the domain layer, not mocked repositories.

      • João Carlos Clementoni Silva

        I have been using AutoFixture to create the fixtures objects. I based on your previous posts and Patrick Lioi’s post. It is very handy for the arrange part.

        For the slowness issue, given a Command that works with a lot of Entities, I am going to at least write one integration test to be sure the pieces work well together and business scenarios I am gonna do as unit tests as you are suggesting.

        Thanks a lot!

  • Dejan Miličić

    Fixie test conventions are revealing traces of BDD. Can you share with us the way you are using Fixie for BDD?

    • jbogard

      Well, it’s not really different, just the names of the class/methods.

  • Chuck Bryan

    I felt like I had a struggle getting xunit setup to run these tests, but, I was able to get it going. In the source code, you are making a call to FixEntryAssembly. There is an article reference, but, unfortunately, I am getting a “Service Unavailable” when I go to that site. What was the purpose for the Fix?

  • Mike Lunn

    I’m really interested in Mediatr as well as this testing concept. One of my request handlers is using a logger instance through DI. And when I run my test using the fixture configuration found in your contoso core example I receieve an unable to resolve service for type ‘Microsoft.Extensions.Logging.ILogger’. I was wondering if you knew off the top of your head a way to add a fake ILoggerFactory to the fixture.