AspNetCore Unit Testing

Disclaimer: No Visual Studio 2017 was ever started during the writing of this post.

This post is going to show how to unit test controllers in AspNetCore.

We are going to do everything using nothing but the dotnet cli and VS Code.

Let's start off by creating a new Web API application.

dotnet new webapi

That's going to set up a minimal Web API application containing a ValuesController

To simplify the example we just return a single string value

Next we are going to implement a service that we will inject into the ValuesController

public interface IService
{
    string GetValue();    
}

public class Service : IService
{
    public string GetValue()
    {
        return "Hello world";
    }
}

Next step is to inject our service into the ValueController

[Route("api/[controller]")]
public class ValueController : Controller
{
    private readonly IService _service;

    public ValueController(IService service) => _service = service;

    // GET api/values
    [HttpGet]
    public string Get()
    {
        return _service.GetValue();
    }        
}

Finally we need to register our service in the Startup class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IService,Service>();
    services.AddMvc();
}

We are now all set and ready for some unit testing.

Unit Testing

To test our controller we will create a test project

dotnet new xunit

We will test our controller using the TestServer from the Microsoft.AspNetCore.TestHost package

dotnet add package Microsoft.AspNetCore.TestHost

Let's create our first test

public class ControllerTests
{
    [Fact]
    public async Task ShouldGetValue()
    {            
        using (var testServer = CreateTestServer())
        {
            var client = testServer.CreateClient();
            var value = await client.GetStringAsync("api/value");
            Assert.Equal("Hello world", value);
        }
    }

    private TestServer CreateTestServer()
    {
            var builder = new WebHostBuilder()                
            .UseStartup<Startup>();                                    
        return new TestServer(builder);                
    }
}

Configurable Server

Let's imagine that we want to test our controller using a mock implementation of IService.

We need to provide a way to override the default container registration before the server is started.

The first step here is to add the ConfigureAdditionalServices method to the Startup class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IService,Service>();
    services.AddMvc();
    ConfigureAdditionalServices(services);
}

protected virtual void ConfigureAdditionalServices(IServiceCollection services)
{
}           

The ConfigureAdditionalServices method gets called after all other services are registered giving us a chance to modify the configuration.

We can now simply inherit from the Startup class and make it configurable.

public class ConfigurableStartup : Startup
{
    private readonly Action<IServiceCollection> configureAction;

    public ConfigurableStartup(IConfiguration configuration, Action<IServiceCollection> configureAction)
        : base(configuration) => this.configureAction = configureAction;

    protected override void ConfigureAdditionalServices(IServiceCollection services)
    {
        configureAction(services);
    }
}

While we could mock services right here in this class, we will make it more versatile by just injecting the configureAtion delegate allowing this class to be used in different scenarios.

public class ConfigurableServer : TestServer
{
    public ConfigurableServer(Action<IServiceCollection> configureAction = null) : base(CreateBuilder(configureAction))
    {
    }

    private static IWebHostBuilder CreateBuilder(Action<IServiceCollection> configureAction)
    {
        if (configureAction == null)
        {
            configureAction = (sc) => {};
        }
        var builder = new WebHostBuilder()
            .ConfigureServices(sc => sc.AddSingleton<Action<IServiceCollection>>(configureAction))
            .UseStartup<ConfigurableStartup>();
        return builder;    
    }
}

Now this might need a little explanation. First we optionally pass inn the configureAction delegate and passes that delegate to the CreateBuilder method that creates the IWebHostBuilder instance that is again passed to the base constructor. The IWebHostBuilder has this ConfigureServices method that can be used to register services that is required by the startup class itself. In this case the ConfigurableStartup class takes the configureAction delegate as a constructor argument and we simply register the delegate as a singleton.

Mocking

With our new ConfigurableServer in place we can start to do some pretty interesting things with regards to mocking services inside our server.

But first, let's install Moq

dotnet add package moq

We can now use the configureAction passed to the ConfigurableServer to replace the originally registered service.

public async Task ShouldGetMockValue()
{
    var serviceMock = new Mock<IService>();
    serviceMock.Setup(m => m.GetValue()).Returns("Hello mockworld");
    var serviceDescriptor = new ServiceDescriptor(typeof(IService), serviceMock.Object);

    using (var testServer = new ConfigurableServer(sc => sc.Replace(serviceDescriptor)))
    {
        var client = testServer.CreateClient();
        var value = await client.GetStringAsync("api/value");
        Assert.Equal("Hello mockworld", value);
    }
}

Want to comment? File an issue here :)