Integration testing your ASP.NET Core middleware using TestServer

Posted on Sunday, 31 Jul 2016

Lately I've been working on a piece of middleware that simplifies temporarily or permanently redirecting a URL from one path to another, whilst easily expressing the permanency of the redirect by sending the appropriate 301 or 302 HTTP Status Code to the browser.

If you've ever re-written a website and had to ensure old, expired URLs didn't result in 404 errors and lost traffic then you'll know what a pain this can be when dealing with a large number of expired URLs. Thankfully using .NET Core's new middleware approach, this task becomes far easier - so much so that I've wrapped it into a library I intend to publish to NuGet:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    app.UseRequestRedirect(r => r.ForPath("/32/old-path.html").RedirectTo("/32/new-path").Permanently());
    app.UseRequestRedirect(r => r.ForPath("/33/old-path.html").RedirectTo("/33/new-path").Temporarily());
    app.UseRequestRedirect(r => r.ForPath("/34/old-path.html").RedirectTo(DeferredQueryDbForPath).Temporarily());
    ...
}

private string DeferredQueryDbForPath(string oldPath){
    /* Query database for new path only if old path is hit */
    return newPath;
}

Whilst working on this middleware I was keen to add some integration tests for a more complete range of tests. After a bit of digging I noticed that doing so was actually really simple, thanks to the _TestServe_r class available as part of the Microsoft.AspNetCore.TestHost package.

What is Test Server?

TestServer is a lightweight and configurable host server, designed solely for testing purposes. Its ability to create and serve test requests without the need for a real web host is it's true value, making it perfect for testing middleware libraries (amongst other things!) that take a request, act upon it and eventually return a response. In the case of the aforementioned middleware, our response we'll be testing will be a status code informing the browser that the page has moved along with the destination the page is located at.

TestServer Usage

As mentioned above, you'll find the TestServer class within the Microsoft.AspNetCore.TestHost package, so first of all you'll need to add it to your test project either by using the following NuGet command:

Install-Package Microsoft.AspNetCore.TestHost

Or by updating your project.json file directly:

"dependencies": {
    ...
    "Microsoft.AspNetCore.TestHost": "1.0.0",
    ...
},

Once the NuGet package has downloaded we're ready start creating our tests.

After creating our test class the first thing we need to do is configure an instance of WebHostBuilder, ensuring we add our configured middleware to the pipeline. Once configured we create our instance of TestServer which then bootstraps our test server based on the supplied WebHostBuilder configurations.

[Fact]
public async void Should_Redirect_Permanently()
{
    // Arrange
    var builder = new WebHostBuilder()
        .Configure(app => {
            app.UseRequestRedirect(r => r.ForPath("/old/").RedirectTo("/new/").Permanently());
        }
    );

    var server = new TestServer(builder);

    // Act
    ...
}

Next we need to manually create a new HTTP Request, passing the parameters required to exercise our middleware. In this instance, using the redirect middleware, all I need to do is create a new GET request to the path outlined in my arrange code snippet above. Once created we simply pass our newly created HttpRequestMessage to our configured instance of TestServer.

// Act
var requestMessage = new HttpRequestMessage(new HttpMethod("GET"), "/old/");
var responseMessage = await server.CreateClient().SendAsync(requestMessage);

// Assert
...

Now all that's left is to assert our test using the response we received from our TestServer.SendAsync() method call. In the example below I'm using assertion library Shouldly to assert that the correct status code is emitted and the correct path (/new/) is returned in the header.

// Assert
responseMessage.StatusCode.ShouldBe(HttpStatusCode.MovedPermanently);
responseMessage.Headers.Location.ToString().ShouldBe("/new/");

The complete test will look like this:

public class IntegrationTests
{
    [Fact]
    public async void Should_Redirect_Permanently()
    {
        // Arrange
        var builder = new WebHostBuilder()
            .Configure(app => {
                app.UseRequestRedirect(r => r.ForPath("/old/").RedirectTo("/new/").Permanently());
            }
        );

        var server = new TestServer(builder);

        // Act
        var requestMessage = new HttpRequestMessage(new HttpMethod("GET"), "/old/");
        var responseMessage = await server.CreateClient().SendAsync(requestMessage);

        // Assert
        responseMessage.StatusCode.ShouldBe(HttpStatusCode.MovedPermanently);
        responseMessage.Headers.Location.ToString().ShouldBe("/new/");
    }
}

Conclusion

In this post we looked at how simple it is to test our middleware using the TestServer instance. Whilst the above example is quite trivial, hopefully it provides you with enough of an understanding as to how you can start writing integration or functional tests for your middleware.

Back