Quantcast
Channel: asp.net 5 rc1 – .NET Liberty
Viewing all articles
Browse latest Browse all 13

Fast ASP.NET 5 Integration Testing with xUnit

$
0
0

In a previous post I showed how I setup some integration tests for a simple ASP.NET 5 Web API project by making use of the TestServer class from the Microsoft.AspNet.TestHost namespace.

This allowed me to fire up a copy of my web application as part of my xUnit test suite, and make HTTP requests against that application. The TestServer class made it super simple to do and I ended up getting some pretty valuable code coverage with minimal effort.

armen-shimoon-fast-x-main

Integration Test Setup

Lets look at my integration test constructor once again:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using Microsoft.AspNet.TestHost;
using MyLifeStack.DataService.Contract;
using Xunit;
 
namespace MyLifeStack.DataService.Test
{
    public class HabitsControllerIntegrationTests
    {
        private readonly TestServer _server;
 
        public HabitsControllerIntegrationTests()
        {
            _server = new TestServer(TestServer.CreateBuilder().UseStartup<Startup>());
        }
    
        // ...
    }
}

As you can see, the amount of code required to get started was pretty minimal. Within each unit test I can just call _server.CreateClient() in order to get an HttpClient that I can use to make requests against that test server. Pretty easy right?

From the looks of it, xUnit will construct my integration tests class, the constructor will create a TestServer, and then all my tests within this class will run, right?

Setup for Multiple Tests

That’s not actually how xUnit works – instead it will recreate my test class for each test that runs within that class! From the xUnit docs:

xUnit.net creates a new instance of the test class for every test that is run, so any code which is placed into the constructor of the test class will be run for every single test. This makes the constructor a convenient place to put reusable context setup code where you want to share the code without sharing object instances (meaning, you get a clean copy of the context object(s) for every test that is run).

That’s interesting. I suppose this is a way to encourage not keeping state between tests and ensuring each test runs independently of one another and regardless of the order they are executed in.

For a simple ASP.NET 5 application, this is probably a good thing. Before each test, a brand new copy of our application will be created and used for the duration of that single test alone. When the next test executes, a fresh, separate copy of the application will be recreated.

Setup Can Be Expensive

For a more complex application however, this might be a bit restrictive. What if our application has some expensive startup logic? Perhaps we need to fetch some resources or warm up an in-memory cache? These operations can be costly – running them again and again before each test might not be a viable option.

In my experience, the frequency at which I run a test suite is inversely proportional to the time it takes to run them. Super quick set of unit tests that execute in 5 seconds? I’d probably execute them after each compile or at least every few saves

Test suite takes 10 minutes? I’ll probably run them once before submitting a code review and hope I got it right the first time.

Enter: Class Fixtures

It seems that the xUnit team was aware of this problem and came up with a solution for it: class fixtures.

With class fixtures, I can define a brand new class, or “fixture”, that performs some shared setup logic.  For example, I can define a TestServerFixture that fires up my TestServer for me:

public class TestServerFixture : IDisposable
{
    public TestServer TestServer { get; }
    public HttpClient Client { get; }

    public TestServerFixture()
    {
        TestServer = new TestServer(TestServer.CreateBuilder().UseStartup<Startup>());
        Client = TestServer.CreateClient();
    }

    public void Dispose()
    {
        TestServer.Dispose();
        Client.Dispose();
    }
}

Next, I need to add a marker interface to my test class to indicate to xUnit that my class requires a given fixture:

public class HabitsControllerIntegrationTests : IClassFixture<TestServerFixture>
{
    private readonly HttpClient _client;

    public HabitsControllerIntegrationTests(TestServerFixture fixture)
    {
        _client = fixture.Client;
    }
    // ...
}

The IClassFixture interface contains no methods – I don’t need to implement anything extra in my test class. It just informs xUnit that I want a TestServerFixture to be created before any test in this class executes (and disposed after the last test completes).

Notice how I’ve added a TestServerFixture parameter to the constructor? This is optional – xUnit will provide the shared fixture instance to my test class before each test runs. If I don’t require the TestServerFixture in my constructor, xUnit will create it regardless.

That helps to illustrate what xUnit is doing: it’s not creating a single copy of HabitsControllerIntegrationTests now that I’ve decorated it with IClassFixture. It will continue to recreate my test class before each individual test – but it will repeatedly provide the same TestServerFixture instance to my test class each time it is constructed.

Updated Tests

Now all I have to do is grab a reference to the HttpClient from the test fixture in my test class constructor and save it to a field. This will allow me to access the shared HttpClient that is configured to talk to the shared TestServer from within each of my tests.

For example:

[Fact]
public async void post_then_get()
{
    // Arrange
    var habit = new Habit
    {
        Name = "Test Habit"
    };
    // Act
    var postResponse = await _client.PostAsJsonAsync("/api/habits", habit);
    var created = await postResponse.Content.ReadAsJsonAsync<Habit>();
    var getResponse = await _client.GetAsync("/api/habits/" + created.HabitId);
    var fetched = await getResponse.Content.ReadAsJsonAsync<Habit>();

    // Assert
    Assert.True(postResponse.IsSuccessStatusCode);
    Assert.True(getResponse.IsSuccessStatusCode);

    Assert.Equal(habit.Name, created.Name);
    Assert.Equal(habit.Name, fetched.Name);

    Assert.NotEqual(Guid.Empty, created.HabitId);
    Assert.Equal(created.HabitId, fetched.HabitId);
}

You Know What They Say About Assuming

There is one gotcha however. Lets take a look at another one of my existing tests:

[Fact]
public async void post_then_get_all()
{
    // Arrange
    var habit = new Habit
    {
        Name = "Test Habit"
    };
    // Act
    using (var client = _server.CreateClient().AcceptJson())
    {
        var postResponse = await client.PostAsJsonAsync("/api/habits", habit);
        var created = await postResponse.Content.ReadAsJsonAsync<Habit>();
        var getResponse = await client.GetAsync("/api/habits/");
        var all = await getResponse.Content.ReadAsJsonAsync<List<Habit>>();
 
        // Assert
        Assert.True(postResponse.IsSuccessStatusCode);
        Assert.True(getResponse.IsSuccessStatusCode);
 
        Assert.Equal(habit.Name, created.Name);
        Assert.NotEqual(Guid.Empty, created.HabitId);
 
        Assert.NotEmpty(all);
        Assert.Equal(created.HabitId, all.Single().HabitId);
        Assert.Equal(created.Name, all.Single().Name);
    }
}

Can you spot the problem?

This test first creates a new Habit, then fetches all Habits and validates that the newly created Habit is the only item returned.

That was fine when a new copy of my Web API was created for each test (since it is using an in-memory repository – all changes are lost after shutting down the application).

But now that I’m reusing the same application across multiple tests, I can’t be sure that this will be the first test to execute, and some other test will likely have already created a new Habit. That means there’s a good chance I’ll get back more than one item.

What does that mean for my tests? The cleanest way to deal with this is to adjust my assertions to make them a bit more robust – they can’t make any assumptions about the current state of my system.

Better Assertions

Here’s what an updated version of that test looks like:

[Fact]
public async void post_then_get_all()
{
    // Arrange
    var habit = new Habit
    {
        Name = "Test Habit"
    };
    // Act
    var postResponse = await _client.PostAsJsonAsync("/api/habits", habit);
    var created = await postResponse.Content.ReadAsJsonAsync<Habit>();
    var getResponse = await _client.GetAsync("/api/habits/");
    var all = await getResponse.Content.ReadAsJsonAsync<List<Habit>>();

    // Assert
    Assert.True(postResponse.IsSuccessStatusCode);
    Assert.True(getResponse.IsSuccessStatusCode);

    Assert.Equal(habit.Name, created.Name);
    Assert.NotEqual(Guid.Empty, created.HabitId);

    Assert.NotEmpty(all);
    var desired = all.SingleOrDefault(x => x.HabitId == created.HabitId && x.Name == created.Name);
    Assert.NotNull(desired);
}

Pretty simple fix – instead of validating that the only returned item is the one I just created – I can validate that there’s at least one item returned and that the one I just created is present in that collection.

Done

Hopefully this helps you to run your integration tests more often. I personally find integration tests extremely valuable – I know at a very high level whether or not my application is running as I expect. If some small piece is broken within my application, an integration test will usually make that pretty obvious.

By making use of the class fixture functionality of xUnit I’m able to run my tests much more rapidly which encourages me to run them more frequently. This helps me develop quicker and detect small mistakes much earlier on in the development cycle.

This is a huge help in tracking down the root cause early and makes my life as a developer that much more enjoyable.

 


Viewing all articles
Browse latest Browse all 13

Latest Images

Trending Articles





Latest Images