Running integration tests for ASP.NET Core apps

Published 7/22/2016 6:59:00 AM
Filed under .NET

One of the things I really disliked about the previous versions of ASP.NET is that
there's no real good way to run integration tests on your web application. You basically
have to set up a full webserver to run integration tests.

Of course if you use Web API 2 or MVC 5 you have the official testhost. It solves a lot
of problems, but the API is a mess to work with and very inflexible.

The story for ASP.NET Core is quite different. You can now do a lot more in your testcode
and it's a lot easier to set up. Let's take a look at what integration testing in ASP.NET core looks like.

Setting things up

Setting up an integration test for ASP.NET core starts by creating a new project.
You can do that from Visual Studio, but I like the commandline better for setting up test projects.

When you run dotnet new -t xunittest you get a fully configured Xunit project with a sample test.
If you set up your project in Visual Studio you have to do the same set up by hand. That's why I like
the commandline better at the moment.

The project.json looks allright, but when you try to restore your packages you well get a few warnings.
This is to be expected, because the .NET Core test runner for Xunit is still in preview.
Update the dotnet-test-xunit and xunit packages to get rid of the warnings.

To write an integration test you need the Microsoft.AspNetCore.TestHost package. Add this to your project.json
and run dotnet restore or wait for Visual Studio to download the package for you.

Write your first test

Now that the project is set up you can write your first integration test.
The structure of an ASP.NET Core integration test looks like this:

[Fact]
public async Task MyFirstIntegrationTest()
{
    var webHostBuilder = new WebHostBuilder()
        .UseStartup<Startup>();

    using (var host = new TestServer(webHostBuilder))
    {
        using (var client = host.CreateClient())
        {
            var requestData = new { name = "Mike" };
            var content = new StringContent(JsonConvert.SerializeObject(requestData), Encoding.UTF8, "applicaiton/json");

            var response = await client.PostAsync("api/myendpoint", content);

            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        }
    }
}

First you need to create a new TestServer instance. It needs access to a รง,
which is pretty much the same thing as you will find in your Program.cs file.

The WebHostBuilder is responsible for configuring your application. You can call
various methods on it to configure your web app. The simplest thing is to call
UseStartup on it and supply your Startup class.

Once you have a test server you can start to perform requests. For this you
first call CreateClient which returns a new HttpClient instance that is
configured to talk to your test server.

Now that you have a test client you can send requests to the server
and verify the results coming back from the server.

Mocking stuff out

The current setup fires up the whole API/Web application that you're testing.
This may or may not be what you want. Sometimes it can be useful to mock out
a few things in your test.

For example if you are talking to WCF services you may want to mock out the agents
for them. You could spin up your WCF services, but that would make the test rather large.

Instead you can modify the services of your application by adding additional lines
to the web host builder.

var webHostBuilder = new WebHostBuilder()
    .UseStartup<Startup>()
    .ConfigureServices(services =>
    {
        services.AddScoped<IMyWcfAgent>(serviceProvider => agentMock.Object);
    });

When you invoke ConfigureServices you can configure custom services for your application.
Within the action or method that you provide it, you can replace services or add additional services.

In the sample above I registered a mock using Moq instead of my regular WCF agent.

Notice: Services that you provide here are added before the services that are configured in your startup class.

In order to make the mocks functional you do need to change your ConfigureServices method in the startup class.
Instead of calling AddScoped, AddInstance, Add or AddTransient you need to call the TryAdd... variant of
the class that you want to replace in your tests.

Calling the TryAdd... methods instead of the regular registration methods doesn't change anything at runtime.
It just means that when a service was registered before it won't be replaced by the one you add it again.
If you use the regular Add... methods you will overwrite your test services.

The ConfigureServices in the startup for the sample above will look like this:

public void ConfigureServices(IServiceCollection services)
{
    services.TryAddScoped<IMyWcfAgent>(serviceProvider => new MyWcfAgent(Configuration["Agents:MyServiceUrl"]));
}

Custom settings

The WebHostBuilder class can do other stuff besides replacing services as well.
One very cool feature is that you can supply custom configuration for your application.

In order to configure custom settings you need to invoke the UseSetting method.
To use the method you need to supply a key and a value for your setting.

var webHostBuilder = new WebHostBuilder()
    .UseStartup<Startup>()
    .UseSetting("Agents:MyServiceUrl", "http://localhost:9200/myservice.svc");

The custom settings are applied after the standard ones. This means that when you specify
a setting in your test it will override the ones loaded by the Startup class.

And there's loads more

Since the TestServer class uses the WebHostBuilder to construct your web application
it's quite flexible in what you can do. All the normal stuff that you'd normally do in your
Program.cs file can be done in your test as well.

This means that there's a lot more you can do in your integration test than what I've
written down here. So get out there and explore your options and have fun!