Support integration tests in .NET Core generic hosting

See original GitHub issue

Problem Statement

Tragically there’s an issue with .NET Core that means that, by default ConfigureTestContainer doesn’t work. See https://github.com/dotnet/aspnetcore/issues/14907

The unsealed ContainerBuilder is useful for working around this shortcoming in ASP.NET Core.

Unfortunately it was sealed in https://github.com/autofac/Autofac/issues/1120

My fear is that without this workaround being available, and without knowing when (or indeed if) it will be fixed in .NET this may end up being a blocker to upgrade from .NET Core 3.1 to .NET 5.

As I see it, those that write integration tests and use AutoFac are going to be unable to upgrade to .NET 5 without first migrating away from AutoFac or forking it. Neither of those is a great outcome. But it’s going to be that or deactivate a whole suite of tests (and that’s not something that’s likely going be possible).

Thanks for all your work on AutoFac BTW - as an open source maintainer myself I appreciate it’s a lot of hard work 🌻❤️

Desired Solution

Could I make an appeal for ContainerBuilder to be unsealed please @tillig?

Alternatives You’ve Considered / Additional Context

My preference would be for this not to be necessary because it’s remedied in .NET itself. My fear (and the motivation for raising this) is imagining the outcome of people finding themselves having to choose between using AutoFac 5.0 forever or give up on integration tests.

cc @davidfowl @Tratcher @anurse @javiercn - I realise you’re very busy people, but it would be tremendous if this could be considered for fixing in .NET generally so such workarounds wouldn’t be necessary. The integration testing that .NET generally supports is tremendous - thanks for your work on it 🤗

@matthias-schuchardt

You can see more details in the linked issue and of the workaround in my blog post here:

https://blog.johnnyreilly.com/2020/05/autofac-webapplicationfactory-and.html

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:32 (9 by maintainers)

github_iconTop GitHub Comments

14reactions
LadislavBohmcommented, Jan 21, 2022

It doesn’t work for me either when using AutoFac Modules because they get called last even after ConfigureTestServices.

Workaround I am using is following.

public class Startup {
    //holds all overrides that might be defined (in tests for example)
    private readonly Queue<Action<ContainerBuilder>> m_AdditionalServiceRegistrations = new();

    public void ConfigureServices(IServiceCollection services) {
        //standard framework services....

       //register Startup class itself to be available in tests
       if (Environment.IsTest()) {
        services.AddSingleton(GetType(), this);
      }
    }

    public void ConfigureContainer(ContainerBuilder builder) {
      //standard module registrations
      builder.RegisterModule<ModuleA>();

      //as last dequeue all available overrides in order and apply them
      while (m_AdditionalServiceRegistrations.TryDequeue(out var registrations)) {
        registrations(builder);
      }
    }
   
    //method called in tests to register possible mock services
    public void AddServiceRegistrationOverrides(Action<ContainerBuilder> registration) {
      m_AdditionalServiceRegistrations.Enqueue(registration);
    }
}

Then define extension method similar to this one

  internal static class HostBuilderExtensions {
    internal static IWebHostBuilder UseAutofacTestServices(this IWebHostBuilder builder, Action<ContainerBuilder> containerBuilder) {
      builder.ConfigureTestServices(services => {
        //startup is registered only in test environment so we can be sure it's available here
        var startup = (Startup)services.Single(s => s.ServiceType == typeof(Startup)).ImplementationInstance!;

        startup.AddServiceRegistrationOverrides(containerBuilder);
      });

      return builder;
    }
}

And finally I can use it in XUnit test with ITestFixture

public class SomeTestClass: IClassFixture<WebApplicationFactory> {
    private readonly WebApplicationFactory m_Factory;

    public SomeTestClass(WebApplicationFactory factory) {
      m_Factory = factory;
    }

    [Fact]
    public async Task SomeTestMethod() {
        await using var updatedFactory = m_Factory.WithWebHostBuilder(builder => {
        builder.UseAutofacTestServices(containerBuilder => {
          containerBuilder.RegisterType<MockService>().As<IService>().SingleInstance();
        });
      });
    }
}

Disadvantage is that you “pollute” your Startup class a bit but I think if it’s a concern to you you can do these modifications in some derived TestStartup class for example.

11reactions
alistairjevanscommented, Oct 1, 2020

I may be missing something here, but it should be possible to use a similar workaround without needing to override the ContainerBuilder. I just tried this:

/// <summary>
/// Based upon https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/test/integration-tests/samples/3.x/IntegrationTestsSample
/// </summary>
/// <typeparam name="TStartup"></typeparam>
public class AutofacWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
    protected override IHost CreateHost(IHostBuilder builder)
    {
        builder.UseServiceProviderFactory(new CustomServiceProviderFactory());
        return base.CreateHost(builder);
    }
}

/// <summary>
/// Based upon https://github.com/dotnet/aspnetcore/issues/14907#issuecomment-620750841 - only necessary because of an issue in ASP.NET Core
/// </summary>
public class CustomServiceProviderFactory : IServiceProviderFactory<ContainerBuilder>
{
    private AutofacServiceProviderFactory _wrapped;
    private IServiceCollection _services;

    public CustomServiceProviderFactory()
    {
        _wrapped = new AutofacServiceProviderFactory();
    }

    public ContainerBuilder CreateBuilder(IServiceCollection services)
    {
        // Store the services for later.
        _services = services;

        return _wrapped.CreateBuilder(services);
    }

    public IServiceProvider CreateServiceProvider(ContainerBuilder containerBuilder)
    {
        var sp = _services.BuildServiceProvider();
#pragma warning disable CS0612 // Type or member is obsolete
        var filters = sp.GetRequiredService<IEnumerable<IStartupConfigureContainerFilter<ContainerBuilder>>>();
#pragma warning restore CS0612 // Type or member is obsolete

        foreach (var filter in filters)
        {
            filter.ConfigureContainer(b => { })(containerBuilder);
        }

        return _wrapped.CreateServiceProvider(containerBuilder);
    }        
}

This then works:

public async Task MyAsync()
{
    void ConfigureTestServices(IServiceCollection services)
    {
        services.AddSingleton("");
    }

    void ConfigureTestContainer(ContainerBuilder builder)
    {
        builder.RegisterInstance("hello world");
    }

    var factory = new AutofacWebApplicationFactory<DefaultStartup>();

    using var client = factory
        .WithWebHostBuilder(builder => {
            builder.ConfigureTestServices(ConfigureTestServices);
            builder.ConfigureTestContainer<ContainerBuilder>(ConfigureTestContainer);
        })
        .CreateClient();
}

@johnnyreilly, perhaps you could try out my alternate workaround and see if I’ve forgotten something?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Integration tests in ASP.NET Core
ASP.NET Core supports integration tests using a unit test framework with a test web host and an in-memory test server. This article assumes...
Read more >
Supporting integration tests with WebApplicationFactory in ...
In this post I look at a related change to ensure that integration testing with WebApplicationFactory works in .NET 6. WebApplicationFactory ...
Read more >
c# - Integration test and hosting ASP.NET Core 6.0 without ...
I have a repo showing the use of WebApplicationFactory<T> for unit/integration tests with Minimal APIs, maybe that will help you out: github.com ...
Read more >
Converting integration tests to .NET Core 3.0
In this post I discuss the changes required to upgrade integration tests that use WebApplicationFactory or TestServer to ASP.NET Core 3.0.
Read more >
Integration Testing: IHost Lifecycle with xUnit.Net
The generic host builder introduced in .Net Core turns out to be a very effective way to bootstrap your system within automated test...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found