Integration Testing with ASP.NET Core 3.1 - Swapping a Dependency with Moq

Investigating how to write integration tests while swapping out dependencies with mocked instances using Moq

Published on 23 December 2019

I was contacted on Twitter by Nick Randolph with a follow up question with regards to my "Swapping a dependency" post.

Question - is it possible to swap out a dependency for one that is mocked using say moq?

The generic AddTransient method has a constraint on it so that you have to use a class which was preventing him from swapping items out. So he was asking if it was possible to swap in a mocked object.

The short answer is "Yes you can".

Let's take a look how.

Swap in a mocked dependency

The trick with this is you have to use the none generic constraint version of the method and the implementationFactory overload.

But how does this help us?

First off we want to define our mock object that we want to use instead of the concrete implementation.

Mock<IMySimpleGithubClient> mockClient = new Mock<IMySimpleGithubClient>();
mockClient.Setup(x => x.GetUserAsync(It.IsAny<string>())).ReturnsAsync(new GithubUser { Name = "My Moq User" });

This is simple enough and pretty standard for people familier with unit testing. We create a Mock<> of the interface we want to swap out and setup an expectation that the method signature, GetUserAsync in this instance, will be called and will explicitly return a GithubUser instance we have specified.

Now how to we swap this in?

We can't use the previous SwapTransient method which was defined earlier in the series as this is purely generic type constrained.

With the AddTransient suite of methods for registering dependencies there is the ability to register a factory method which generates the instance of the type. This is our way in!

/// <summary>
/// Adds a transient service of the type specified in <paramref name="serviceType" /> with a
/// factory specified in <paramref name="implementationFactory" /> to the
/// specified <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" /> to add the service to.</param>
/// <param name="serviceType">The type of the service to register.</param>
/// <param name="implementationFactory">The factory that creates the service.</param>
/// <returns>A reference to this instance after the operation has completed.</returns>
/// <seealso cref="F:Microsoft.Extensions.DependencyInjection.ServiceLifetime.Transient" />
public static IServiceCollection AddTransient(
    this IServiceCollection services,
    Type serviceType,
    Func<IServiceProvider, object> implementationFactory);

This can accept a simple lambda or more complicated structure, but for our usages it will be simple. Unfortunately using this method prevents us from using some nice generic abilities. However, not all is lost as we not calling this directly from our tests. The inner workings of the extension method we are about to write is an implementation detail and the method itself will still have a clean API to work with.

Extension method with implementation factory

Taking inspiration from both the original SwapTransient extension method as well as the generic and none generic AddTransient varients we can look to offer the clean api surface for the test usages.

/// <summary>
/// Removes all registered <see cref="ServiceLifetime.Transient"/> registrations of <see cref="TService"/> and adds a new registration which uses the <see cref="Func{IServiceProvider, TService}"/>.
/// </summary>
/// <typeparam name="TService">The type of service interface which needs to be placed.</typeparam>
/// <param name="services"></param>
/// <param name="implementationFactory">The implementation factory for the specified type.</param>
public static void SwapTransient<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)
{
    if (services.Any(x => x.ServiceType == typeof(TService) && x.Lifetime == ServiceLifetime.Transient))
    {
        var serviceDescriptors = services.Where(x => x.ServiceType == typeof(TService) && x.Lifetime == ServiceLifetime.Transient).ToList();
        foreach (var serviceDescriptor in serviceDescriptors)
        {
            services.Remove(serviceDescriptor);
        }
    }

    services.AddTransient(typeof(TService), (sp) => implementationFactory(sp));
}

As before there is a limitation of all the types registered previously of TService will be removed. We then add a new transient registration of TService passing in the Func<IServiceProvider, TService> provided.

The method overload we are using here is expecting Func<IServiceProvider, object> but as object is the base building block of C# this is fine.

Using the new extension method

Now we have the new extension method and all it's strongly typed generic goodness how do we use this in our integration test?

// Arrange
Mock<IMySimpleGithubClient> mockClient = new Mock<IMySimpleGithubClient>();
mockClient.Setup(x => x.GetUserAsync(It.IsAny<string>()))
    .ReturnsAsync(new GithubUser { Name = "My Moq User" });

var factory = new CustomWebApplicationFactory<WebApplication41.Startup>(services =>
{
    // setup the swaps
    services.SwapTransient<IMySimpleGithubClient>(provider => mockClient.Object);
});

We setup the dependency as we would do in a unit test and then pass the mockClient.Object in though to the SwapTransient method with a simple lambda expression. The provider input parameter is the IServiceProvider which the AddTransient method under the hood requires (see the extension method).

Full integration test

[Fact]
public async Task Test_WithMoq()
{
    // Arrange
    Mock<IMySimpleGithubClient> mockClient = new Mock<IMySimpleGithubClient>();
    mockClient.Setup(x => x.GetUserAsync(It.IsAny<string>())).ReturnsAsync(new GithubUser { Name = "My Moq User" });

    var factory = new CustomWebApplicationFactory<WebApplication41.Startup>(services =>
    {
        // setup the swaps
        services.SwapTransient<IMySimpleGithubClient>(provider => mockClient.Object);
    });

    // Create an HttpClient which is setup for the test host
    var client = factory.CreateClient();

    // Act
    var response = await client.GetAsync("/");

    // Assert
    response.EnsureSuccessStatusCode();
    var responseString = await response.Content.ReadAsStringAsync();

    var item = System.Text.Json.JsonSerializer.Deserialize<GithubUser>(responseString);
    item.Name.Should().Be("My Moq User");

    mockClient.Verify(x => x.GetUserAsync(It.IsAny<string>()), Times.Once);
}

When we debug through the integration test and put a break point in the controller action being tested you can see the dependency, provided from [FromServices], is the mocked object we've just registered.

Usage of the mocked dependency

Conclusion

In this post we have continued to look at swapping out dependencies for integration tests, specifically how to use a mocking framework like Moq to provide the mocked dependency. Due to the nature of what we are registering we have had to harness the none generic IServiceProvider extension methods but kept SwapTransient api surface generically constrained to aid with consuming the api.

Any questions/comments then please contact me on Twitter @WestDiscGolf

Part 1 - Integration Testing with ASP.NET Core 3.1

Part 2 - Integration Testing with ASP.NET Core 3.1 - Testing Your Application

Part 3 - Integration Testing with ASP.NET Core 3.1 - Swapping a dependency

Part 4 - Integration Testing with ASP.NET Core 3.1 - Remove the Boiler Plate

Part 5 - This post