Upcoming book:

Modern .NET Development with Azure and DevOps

ASP.NET Core integration tests with NUnit and Moq

Introduction

ASP.NET Core has an extremely useful integration testing framework, in the form of the NuGet package Microsoft.AspNetCore.Mvc.Testing. The official documentation is a great starting point if you haven't used the framework before.

To summarize it, the idea behind this framework is to allow you to perform HTTP requests against your application (regardless of Razor Pages, MVC, API, gRPC, etc) while allowing you to control how that application is configured - in other words, changing the Startup configuration to suit the tests need.

A common requirement is to be able to run these tests as part of a build pipeline, which, more often than not, comes together with the need to mock some external services that either shouldn't be called, or for some network constraint, cannot be called.

Fortunately, these tests can be run from NUnit like any other test, and can run in parallel as well, which makes this task much easier.

I'll be using the API template with the WeatherForecast controller for this post, just to keep things simple.

I've created a .NET Tool to auto-generate simple tests akin to the ones in this post. See here: GitHub repo.

Setting up the environment

This section is only meant to introduce the classes needed for this post to make sense. If you already have a project with the services you want to mock, feel free to skip to the next section.

For this tutorial, we will be using just 2 services, a Domain service, called TemperatureService:

public interface ITemperatureService
{
    Task<string> GetTemperatureFromExternalApiAsync();
}

public class TemperatureService : ITemperatureService
{
    private readonly ITemperatureApiClient _temperatureApiClient;

    public TemperatureService(ITemperatureApiClient temperatureApiClient)
    {
        _temperatureApiClient = temperatureApiClient;
    }

    public async Task<string> GetTemperatureFromExternalApiAsync()
    {
        return await _temperatureApiClient.GetTemperatureAsync();
    }
}

And an Infrastructure service, called TemperatureApiClient:

public interface ITemperatureApiClient
{
    Task<string> GetTemperatureAsync();
}

public class TemperatureApiClient : ITemperatureApiClient
{
    private readonly HttpClient _httpClient;

    public TemperatureApiClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public Task<string> GetTemperatureAsync()
    {
        return Task.FromResult("Temperature came from a real service");
    }
}

Creating the integration test project

My preference is to have these integration tests apart from unit tests, both for separation of concerns and because the integration tests package requires the project to use the .NET Core Web SDK, which adds more dependencies.

The most basic project would then look like this:

<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <IsPackable>false</IsPackable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.14" />
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
        <PackageReference Include="Moq" Version="4.18.4" />
        <PackageReference Include="NUnit" Version="3.13.3" />
        <PackageReference Include="NUnit.Analyzers" Version="3.6.0" />
        <PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
        <PackageReference Include="coverlet.collector" Version="3.2.0" />
    </ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\WebApplication1\WebApplication1.csproj" />
    </ItemGroup>
</Project>

Adding a mock wrapper

This is, again, my personal preference, but I found it to be very useful when the number of services that need to be mocked grows to more than 5. The idea is to have a single class where mocks are added and pre-configured, if needed.

Now, there are at least two ways to do this:

  • The first one is to mock only external services that the tests can/should not use (like databases, email services, etc), and hence test as much as possible with these tests, which might include some business logic, validations and possibly even communication between domain services.

  • The second one is to mock the domain/service layer, and so test only the entry points (Controllers, gRPC handlers, etc).

For this tutorial, I will follow the first approach, but the process for the second approach is the same, the difference being in the services that are mocked.

public class ExternalServicesMock
{
    public Mock<ITemperatureApiClient> TemperatureApiClient { get; }

    public ExternalServicesMock()
    {
        TemperatureApiClient = new Mock<ITemperatureApiClient>();
    }

    public IEnumerable<(Type, object)> GetMocks()
    {
        return GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
            .Select(x =>
            {
                var underlyingType = x.PropertyType.GetGenericArguments()[0];
                var value = x.GetValue(this) as Mock;

                return (underlyingType, value.Object);
            })
            .ToArray();
    }
}

A bit of reflection used in GetMocks allows the factory to always have the entire list of mocks without having to manually add the mocks to a list, which can sometimes be forgotten and result in time wasted trying to identify the issue.

Creating the custom Web Application Factory

In order to perform the configuration for the app, as well as inject our mocks, we need a class that inherits from WebApplicationFactory<TStartup>.

public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
    private readonly ExternalServicesMock _externalServicesMock;

    public CustomWebApplicationFactory(ExternalServicesMock externalServicesMock)
    {
        _externalServicesMock = externalServicesMock;
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("IntegrationTesting");
        base.ConfigureWebHost(builder);

        builder
            .ConfigureServices(services =>
            {
                foreach ((var interfaceType, var serviceMock) in _externalServicesMock.GetMocks())
                {
                    services.Remove(services.SingleOrDefault(d => d.ServiceType == interfaceType));

                    services.AddSingleton(interfaceType, serviceMock);
                }
            });
    }
}

Since I'm using the default template, we don't have anything else to configure except replacing services with mocks. Specifying the IntegrationTesting environment, however, gives us 2 very important benefits: being able to have an appsetings.integrationtesting.json file picked up automatically, and being able to use constructs like:

if (_hostingEnvironment.IsEnvironment("IntegrationTesting"))
{
    // do something exclusively for this environment
}

If the amount of changes that are needed for this environment is large enough, or if you want to keep configuration separated, you can always create a separate Startup class only for this environment.

Creating a base test class

We need a base test class to abstract the Web Application Factory, as otherwise each test needs to copy-paste the same boilerplate code, which is error-prone and hard to update if something needs changing.

public class CustomBaseTest
{
    private readonly CustomWebApplicationFactory _webApplicationFactory;
    public ExternalServicesMock ExternalServicesMock { get; }

    public CustomBaseTest()
    {
        ExternalServicesMock = new ExternalServicesMock();
        _webApplicationFactory = new CustomWebApplicationFactory(ExternalServicesMock);
    }

    public HttpClient GetClient() => _webApplicationFactory.CreateClient();
}

Notice that we are leaving public the mock wrapper but hiding the web application factory. This allows us to configure mocks without polluting the children of the class. In some circumstances, however, some other parts of the WebApplicationFactory might need to be configured for some tests.

Using the CustomBaseTest class in a test

With all the plumbing code now in place, all that is left is to create a test to use this base class.

[TestFixture]
public class TemperatureTests : CustomBaseTest
{
    [Test]
    public async Task GetTemperatureTest()
    {
        var expected = "test";
        ExternalServicesMock.TemperatureApiClient
            .Setup(x => x.GetTemperatureAsync())
            .ReturnsAsync(expected);

        var client = GetClient();

        var response = await client.GetAsync("/weatherforecast/temperature");
        var responseMessage = await response.Content.ReadAsStringAsync();

        Assert.AreEqual(expected, responseMessage);
        ExternalServicesMock.TemperatureApiClient.Verify(x => x.GetTemperatureAsync(), Times.Once);
    }
}

If everything went well in the previous steps, the test should be green.

Putting it all together

We now have the entire solution, and from the TemperatureTests class it should be fairly straightforward to see that this is quite extensible and reusable.

The source code for this solution can be found on my GitHub page: https://github.com/CamiloTerevinto/Blog.

I hope you found this post useful, and if you have any questions, comments, ideas... feel free to leave a comment!