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.
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
Other Posts In This Series
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
Part 6 - Integration Testing with ASP.NET Core 3.1 - Set Default Headers for All Clients