Abstracting DA in integration tests with .NET Core

New version of .NET Core 2.1 brings us really powerful technique for doing integration tests. More specifically it is ability to run in memory server, so we can easily make queries against our SUT (System Under Test). Even more specifically it is WebApplicationFactory class in Microsoft.AspNetCore.Mvc.Testing namespace.

What it means for as is that we do not have to run our whole application to be able to to call API we would like to test, it will run in memory.

This is all nice but i had one problem with this. Official msdn examples are using in-memory database that is using entity framework ORM to handle persistence. But this implies that we are calling database directly in our SUT. If that is your case you can use following code when overriding ConfigureWebHost method in WebApplicationFactory and you are done.

services.AddDbContext(options =>
{
    options.UseInMemoryDatabase("InMemoryDbForTesting");
    options.UseInternalServiceProvider(serviceProvider);
});      

However, there is not given alternative how to do integration tests when you are not using entity framework db context directly in your controllers. Sure you can just swap connection string before test run but then you need to maintain state of the database. That is difficult because you will have to tear down and seed database every single time. You will need a seed script to be in sync with data model. These are huge downsides. So I came up with mine solution that is not perfect but it works just fine for me.

How it works
We are able to set environment variables and inject dependencies before test run by inheriting from WebApplicationFactory class. That means we are also able to inject our fake repositories that will be used to abstract DA.

public class CustomWebApplicationFactory : WebApplicationFactory
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    {
        return base.CreateWebHostBuilder().UseEnvironment("test");
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        // Inject test dependecies to SUT
        builder.ConfigureServices(services =>
        {
            services.AddTransient();
        });
    }
}

Unfortunately we also need to tell in our SUT Startup file to not use real dependencies under the test. This is needed because SUT still need to bootstrap the application. That means real dependencies specified there will override our test dependencies. We can do this by checking for current environment. That is why we defined “test” environment in example above.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    if (!HostingEnvironment.IsEnvironment("test"))
    {
        services.AddTransient();
    }

}

If you do not like hardcoded strings you can use environment specific dependencies, like in this post

Do we really need to test Data access logic ?
I know that most of you is thinking something like “But i would like to test my data access in integration tests. They should check as many interactions as possible, geez.” and you are kind of right. I think it depends on the need, more specifically it depends how your application is structured and how many responsibilities your DA layer have. In ideal world it should have only one and that is storing/retrieving data stored in database. Mine DA layer contain just entity framework logic for this purpose and I have no need to test that code because it would be like testing if third party libraries work as they should. That is not my responsibility.

Implementation details
I won’t go into details how to run this kind of integration tests in this article because it is nothing new, same thing is already described on msdn

My working example is hosted on github

However I describe few important things. Solution contains Core project that is referencing both API and Integration test projects. This Core project contains real and fakes repositories, domain objects and stubs that are being manipulated in fake repositories.

I created base fake repository that contain CRUD actions and can be inherited in specific fake repositories. So all that is left to do in our specific fake repositories is to inherit from this base class and pass stubs to base constructor. That means it scales quite well as long as you need just CRUD functions.

public class BaseRepositoryFake where T : DomainBase
{
    private List stubs = new List();

    public BaseRepositoryFake(List stubs)
    {
        this.stubs = stubs;
    }

    public T Create(T stub)
    {
        var id = stubs.Max(s => s.Id) + 1;
        stub.Id = id;
        stubs.Add(stub);
        return Get(stub.Id);
    }

    public bool Delete(int id)
    {
        var stub = Get(id);
        return stub == null ? false : stubs.Remove(stub);
    }

    public T Get(int id)
    {
        return stubs.SingleOrDefault(f => f.Id == id);
    }

    public T Update(T stub)
    {
        Delete(stub.Id);
        return Create(stub);
    }
}

public class FooRepositoryFake : BaseRepositoryFake, IFooRepository
{
    public FooRepositoryFake() : base(FooStubs.Foos)
    { }
}

This is how CRUD tests looks like.

public class FooControllerTest
{
    protected readonly HttpClient client;

    protected readonly CustomWebApplicationFactory factory =
        new CustomWebApplicationFactory();

    private readonly string apiBaseUrl = "/api/foo";

    public FooControllerTest()
    {
        this.client = factory.CreateClient();
    }

    [Fact]
    public async Task ShouldGetCorrectFooById()
    {
        // Arrange
        var expectedFoo = FooStubs.Foos[0];
        var url = $"{apiBaseUrl}/{expectedFoo.Id}";

        // Act
        var response = await client.GetAsync(url);
        var foo = await response.GetResponseContent();

        // Assert
        response.EnsureSuccessStatusCode(); // Status Code 200-299
        Assert.True(FooStubs.Matches(expectedFoo, foo));
    }

    [Fact]
    public async Task ShouldCreateFoo()
    {
        // Arrange
        var expectedFoo = new Foo()
        {
            Name = "Lorem ipsum",
        };
        var fooToBeCreated = expectedFoo.GetAsSerializedString();

        // Act
        var response = await client.PostAsync(apiBaseUrl, fooToBeCreated);
        var createdFoo = await response.GetResponseContent();

        // Assert
        Assert.True(FooStubs.Matches(expectedFoo, createdFoo));
    }

    [Fact]
    public async Task ShouldUpdateFoo()
    {
        // Arrange
        var expectedFoo = FooStubs.Foos[0];
        expectedFoo.Name = "UpdatedName";
        var fooToBeUpdated = expectedFoo.GetAsSerializedString();

        // Act
        var response = await client.PutAsync(apiBaseUrl, fooToBeUpdated);
        var updatedFoo = await response.GetResponseContent();

        // Assert
        Assert.True(FooStubs.Matches(expectedFoo, updatedFoo));
    }

    [Fact]
    public async Task ShouldDeleteFoo()
    {
        // Arrange
        var fooId = FooStubs.Foos[0].Id;
        var url = $"{apiBaseUrl}/{fooId}";

        // Act
        var response = await client.DeleteAsync(url);

        // Assert 
        Assert.True(response.IsSuccessStatusCode);
    }
}

These are test extensions that I am using, you can just copy paste it to your project 🙂

public static class Extensions
{
    public static async Task GetResponseContent(this HttpResponseMessage response)
    {
        var responseContentAsString = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject(responseContentAsString);
    }

    public static StringContent GetAsSerializedString(this object value)
    {
        return new StringContent(
            JsonConvert.SerializeObject(value),
            System.Text.Encoding.UTF8, "application/json");
    }
}

I hope this helps you, feel free to leave any feedback in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *