Search code examples
.netrazorasp.net-core

Replace service with mocked using WebApplicationFactory


I'm trying to replace an implementation of IMessageFormatter with a MockMessageFormatter for a test.

public class MyMockingWebApplicationFactory<TStartUp> : WebApplicationFactory<Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services => { services.AddTransient<IMessageFormatter, MockMessageFormatter>(); });
    }
}

The test looks like so:

public class MockingTests : IClassFixture<MyMockingWebApplicationFactory<Startup>>
{
    public MockingTests(MyMockingWebApplicationFactory<Startup> webApplicationFactory)
    {
        _webApplicationFactory = webApplicationFactory;
    }

    private readonly MyMockingWebApplicationFactory<Startup> _webApplicationFactory;

    [Fact]
    public async Task MockIt()
    {
        var client = _webApplicationFactory.WithWebHostBuilder(builder =>
        {
            //builder.ConfigureServices(services =>
            //{
            //    var foo = ServiceDescriptor.Transient<IMessageFormatter, MessageFormatter>();
            //    services.Remove(foo);
            //    services.AddTransient<IMessageFormatter, MockMessageFormatter>();
            //});
        }).CreateClient();

        var message = "Hello";
        var expectedReversedMessage = "foo";
        var result = await client.GetAsync($"/?message={message}");
        var content = await result.Content.ReadAsStreamAsync();
        var htmlParser = new HtmlParser();
        var htmlDocument = await htmlParser.ParseAsync(content);
        var messageElement = htmlDocument.GetElementById("original-message");
        var reversedMessageElement = htmlDocument.GetElementById("reversed-message");
        Assert.Equal(message, messageElement.InnerHtml);
        var reversedMessage = reversedMessageElement.InnerHtml;
        Assert.Equal(expectedReversedMessage, reversedMessage);
    }
}

I've tried adding the call to ConfigureServices in the client and in the MyMockingWebApplicationFactory but the problem seems to be that the real Startup class is executed after the test's registration and so overwrites them.

The real StartUp class is:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); ;
        services.AddTransient<IMessageFormatter, MessageFormatter>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseDeveloperExceptionPage();
        app.UseMvc();
    }
}

Solution

  • replace your code :

    builder.ConfigureServices(services => { services.AddTransient<IMessageFormatter, MockMessageFormatter>(); });
    

    to:

    builder.ConfigureTestServices(services =>
            {
                var serviceProvider = services.BuildServiceProvider();
                var descriptor =
                    new ServiceDescriptor(
                        typeof(IMessageFormatter),
                        typeof(MockMessageFormatter),
                        ServiceLifetime.Transient);
                services.Replace(descriptor);
            });