simpleinjector / SimpleInjector.Integration.AspNetCore

MIT License
2 stars 3 forks source link

Using SimpleInjector in integration tests via WebApplicationFactory #29

Open skuzminoff opened 2 years ago

skuzminoff commented 2 years ago

Prerequisites:

I need to replace some of the services registered via Simple Injector for test purposes. It could be easily done using net core built-in DI container. But i found it impossible to implement with Simple Injector.

Default extension points in WebApplicationFactory for doing this are either .ConfigureTestServices() or .ConfigureTestContainer().

The latter is not an option considering this issue https://github.com/dotnet/aspnetcore/issues/14907

But I couldn't do it via .ConfigureTestServices() either.

I can not get Container instance from IServiceCollection (it's simply not there during the .ConfigureTestServices()).

    public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureTestContainer<Container>(container =>
            {
                //useless, it's impossible to get here
            });

            builder.ConfigureTestServices(services =>
            {
                var container =
                    (Container)services
                    // System.InvalidOperationException: Sequence contains no matching element
                    .Last(d => d.ServiceType == typeof(Container)) 
                    .ImplementationInstance;

                container.Options.AllowOverridingRegistrations = true;
                var testServiceMock = new Mock<ITestService>();
                container.Register(() => testServiceMock.Object);

            });
        }
    }

Even if I hack the actual Container Instance from Startup.cs and try to register service mock, I got "Container is locked" error.

So, I'd like to know, if there is a way to use Simple Injector in such cases and, if positive, how do I do that.

dotnetjunkie commented 2 years ago

I tried your code, but it actually works on my machine. Could it be that you didn't call services.AddSimpleInjector from inside your Startup.ConfigureServices method? This method adds the Container to the IServiceCollection.

skuzminoff commented 2 years ago

I tried to follow documentation https://docs.simpleinjector.org/en/latest/aspnetintegration.html

But anyway here is configuration code from the Startup.cs:

public class Startup
{
    //....
    private readonly Container _container = new Container();

    //...

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(...);
        //...

        services.AddSimpleInjector(_container, opts =>
        {
            opts.AddAspNetCore()
                .AddControllerActivation();

            opts.AddLogging();
        });

        SimpleInjectorConfig.Configure(...);

        ///.....
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseSimpleInjector(_container);

        // ....

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });

        // ....

        _container.Verify(VerificationOption.VerifyAndDiagnose);
    }
}
dotnetjunkie commented 2 years ago

Try this:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(new SimpleInjector.Container());
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
    }
}

This adds the container to the collection. If you can't retrieve that instance in your test, there is likely something happening that your code doesn't show. As I said, I tried to reproduce the issue, but a call to .Last(d => d.ServiceType == typeof(Container)) results in a returned registration.

skuzminoff commented 2 years ago

Thank you, @dotnetjunkie, your snippet helped.

Btw, I've looked through the sources and haven't found the exact place where SimpleInjector.Container is being added to the IServiceCollection. Can you, please, point me to the place?

dotnetjunkie commented 2 years ago

Can you, please, point me to the place?

https://github.com/simpleinjector/SimpleInjector.Integration.AspNetCore/blob/master/src/SimpleInjector.Integration.ServiceCollection/SimpleInjectorServiceCollectionExtensions.cs#L67

skuzminoff commented 2 years ago

@dotnetjunkie Interesting. There is no adding container to IServiceCollection at version 4.8. https://github.com/simpleinjector/SimpleInjector/blob/7c8b3dc653c8115291da8f20272f148eafbdfe15/src/SimpleInjector.Integration.ServiceCollection/SimpleInjectorServiceCollectionExtensions.cs#L65

Probably, somewhere else or this issue has been fixed in a latter version.

Anyway, thanks for help, i really appreciate it.

dotnetjunkie commented 2 years ago

Ah, that very well could be true. Initially, the container wasn't added. I'm unsure whoch version it was added.

But this means that, as long as you're on 4.8, its best to manually add the container to rhe service collection.