Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Autofac does not register open generic in test project #99

Closed
amunim opened this issue Feb 2, 2022 · 2 comments
Closed

Autofac does not register open generic in test project #99

amunim opened this issue Feb 2, 2022 · 2 comments
Labels

Comments

@amunim
Copy link

amunim commented Feb 2, 2022

Describe the bug
I am trying to use autofac to register my open bound generic Mediator handlers, which works in my main project but does not in my tests(using Mytest.Aspnet.Mvc library)

My constraints are to class which inherits my main layout Model.

public class Main
    {
        public class Query<T> : IRequest<Result<T>> where T : MainLayout, new()
        {
             //some props
        }
        public class Handler<T> : IRequestHandler<Query<T>, Result<T>> where T : MainLayout, new()
        {
             public async Task<Result<T>> Handle(Query<T> request, CancellationToken cancellationToken)
            {
            }
         }
    }
Test Startup
public class TestStartup : Startup
    {
        protected static Container container = new();
        // Prepare additional server configuration - add AutoFac here. 
        // Call 'IsRunningOn' either in the static constructor of the TestStartup class
        // or in an assembly initialization method if your test runner supports it.
        // Note that 'IsRunningOn' should be called only once per test project.
        static TestStartup()
            => MyApplication
                .IsRunningOn(server => server
                    .WithServices(services =>
                    {
                        ContainerBuilder bldr = new();
                        bldr.Populate(services);
                        //services.AddSingleton(bldr.Build());
                        services.AddAutofac(bldr =>
                        {
                            bldr.RegisterGeneric(typeof(Sunspot.Application.Main.Main.Handler<>)).AsImplementedInterfaces();
                            bldr.RegisterGeneric(typeof(Sunspot.Application.Core.Main<,,>.Handler)).AsImplementedInterfaces();
                            bldr.RegisterGeneric(typeof(Sunspot.Application.Core.Main<,>.Handler)).AsImplementedInterfaces();
                        });
                        //services.AddSimpleInjector(container);
                        //server.WithConfiguration(x => x.Add((new AutofacServiceProviderFactory())));
                    }));

        public TestStartup(IWebHostEnvironment env) : base(env)
        {
            //env.WebRootPath = @"D:\projects\office\SVR\AspServer\wwwroot";
            var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
            _config = builder.Build();

            env.WebRootPath = _config.GetValue<string>("webRoot");
        }

        public void ConfigureTestServices(IServiceCollection services)
        {
            base.ConfigureServices(services);
            services.AddMediatR(typeof(Sunspot.Application.Album.AlbumLayout.Handler));
            services.AddOptions();
            // services.AddAutofac(ConfigureTestContainer);
            services.AddTransient(typeof(Sunspot.Application.Main.Main.Handler<>));
            services.AddTransient(typeof(Sunspot.Application.Core.Main<,,>.Handler));
            services.AddTransient(typeof(Sunspot.Application.Core.Main<,>.Handler));

            container.Register(typeof(Sunspot.Application.Main.Main.Handler<>));
            container.Register(typeof(Sunspot.Application.Core.Main<,,>.Handler));
            container.Register(typeof(Sunspot.Application.Core.Main<,>.Handler));

            //services.AddOptions(container);
        }

        public void ConfigureTestContainer(ContainerBuilder builder)
        {
            base.ConfigureContainer(builder);
            builder
                .RegisterAssemblyTypes(typeof(Sunspot.Application.Main.Main.Handler<>).Assembly)
                .AsClosedTypesOf(typeof(Sunspot.Application.Main.Main.Handler<>))
                .AsImplementedInterfaces();
            builder
                .RegisterAssemblyTypes(typeof(Sunspot.Application.Main.Main.Query<>).Assembly)
                .AsClosedTypesOf(typeof(Sunspot.Application.Main.Main.Query<>))
                .AsImplementedInterfaces();

            // builder.RegisterAssemblyOpenGenericTypes(typeof(Sunspot.Application.Main.Main.Handler<>).Assembly).AsImplementedInterfaces();
            //builder.RegisterAssemblyTypes(typeof(Sunspot.Application.Main.Main.Handler<>).Assembly).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(Sunspot.Application.Main.Main.Handler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(Sunspot.Application.Core.Main<,,>.Handler)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(Sunspot.Application.Core.Main<,>.Handler)).AsImplementedInterfaces();
        }
    }

Full exception with stack trace:
Message:
MyTested.AspNetCore.Mvc.Exceptions.InvocationAssertionException : When calling Index action in UnitKeysController expected no exception but AggregateException (containing InvalidOperationException with 'Handler was not found for request of type MediatR.IRequestHandler2[Sunspot.Application.Main.Main+Query1[Sunspot.Models.DTO.Dashboard.UnitRatesDTO],Result1[Sunspot.Models.DTO.Dashboard.UnitRatesDTO]]. Register your handlers with the container. See the samples in GitHub for examples.' message) was thrown without being caught. Stack Trace: InvocationValidator.CheckForException(Exception exception, String exceptionMessagePrefix) ActionResultTestBuilder1.ShouldReturn()

[put exception here between fences]

Assembly/dependency versions:


  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="appsettings.Development.json" />
    <None Remove="appsettings.json" />
  </ItemGroup>

  <ItemGroup>
    <Content Include="appsettings.Development.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </Content>
    <Content Include="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Autofac" Version="6.3.0" />
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0" />
    <PackageReference Include="FluentAssertions" Version="6.2.0" />
    <PackageReference Include="FluentValidation" Version="10.3.6" />
    <PackageReference Include="FluentValidation.AspNetCore" Version="10.3.6" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
    <PackageReference Include="Moq" Version="4.16.1" />
    <PackageReference Include="MyTested.AspNetCore.Mvc" Version="5.0.0" />
    <PackageReference Include="SimpleInjector" Version="5.3.3" />
    <PackageReference Include="SimpleInjector.Integration.ServiceCollection" Version="5.3.0" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="1.3.0">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\AspServer\Sunspot.csproj" />
  </ItemGroup>

</Project>

Additional context

public class Startup
    {
        protected IConfiguration _config;
        public Startup(IWebHostEnvironment env)
        {
            if (env.IsProduction()) //for hosting in pdc3
                env.WebRootPath = @"R:\";

            var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
            _config = builder.Build();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers().AddNewtonsoftJson(o =>
            {
                o.SerializerSettings.Converters.Add(new StringEnumConverter
                {
                    NamingStrategy = new CamelCaseNamingStrategy()
                });
            });
            services.AddDbContext<ApplicationDbContext>(opt =>
            {
                opt.UseSqlServer(_config.GetConnectionString("DefaultConnection"));
            });
            services.AddCors(opt =>
            {
                opt.AddPolicy("CorsPolicy", policy =>
                {                    policy.AllowAnyMethod().AllowAnyHeader().WithExposedHeaders("Pagination").WithOrigins("http://localhost:3000");
                });
            });

            services.AddAutoMapper(typeof(Startup));
            services.AddAutoMapper(typeof(List.Handler));
            services.AddAutoMapper(typeof(Sunspot.Application.BrowseAllRentals.GetAll.Handler));

            services.AddMediatR(typeof(Startup));
            services.AddMediatR(typeof(List.Handler));
            services.AddMediatR(typeof(Sunspot.Application.BrowseAllRentals.GetAll.Handler));
            
            services.AddMediatR(typeof(Application.Main.Main.Query<>), typeof(Application.Main.Main.Handler<>));
            services.AddMediatR(typeof(Main<,>));
            services.AddMediatR(typeof(Main<,,>));

            services.AddMvcCore().AddApiExplorer();
            //services.AddTransient<IRequestHandler<Application.Core.Main<T_Specials, T_SpecialDomains>.Query, List<Application.Core.Main<T_Specials, T_SpecialDomains>.Data>>, Application.Core.Main<T_Specials, T_SpecialDomains>.Handler>();
           //do not need the above line(for each concrete type that uses this generic query) thanks to autofac :)
        }

        public void ConfigureContainer(ContainerBuilder builder)
        {
            builder.RegisterGeneric(typeof(Application.Main.Main.Handler<>)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(Main<,,>.Handler)).AsImplementedInterfaces();
            builder.RegisterGeneric(typeof(Main<,>.Handler)).AsImplementedInterfaces();
        }
    }
@tillig
Copy link
Member

tillig commented Feb 2, 2022

I'm going to close this and recommend you do one or more of these things:

  • File an issue with MyTested.AspNetCore.Mvc. This seems to be how you're initiating testing which makes it the thing responsible for orchestrating the test startup where things are registered.
  • Check out this .NET Core generic hosting issue which shows that as of .NET Core 3.x the ConfigureTestContainer no longer works. There's nothing Autofac can do about that, it needs to be taken up with the .NET Core team.
  • Ask about it on StackOverflow and make sure you tag stuff other than just autofac. I would also recommend trimming this way, way down when you post there because there is honestly way too much for a minimal repro. Create a brand new, very tiny project that maybe tests one controller with one dependency - no one will be able to adequately piece through everything. For example, do you need to include the setup of appsettings.json in the repro? Pull that out - it likely doesn't affect the DI here... and if it does, then you know you've found your culprit. Minimal but complete is the key.
  • Remove SimpleInjector. I would very strongly recommend you not try to have two different backing DI container libraries. It will just confuse things.

Basically, there's not a bug in Autofac here - if your test isn't wiring up the things into the container at the right spot, that's a problem with your code in the test or test setup. The Autofac.Extensions.DependencyInjection library can take the Microsoft format registrations and convert them into Autofac, but it doesn't actually execute the registration code or anything else. Given there are only a couple of folks actively maintaining Autofac and all the extension libraries, we unfortunately don't have time to dig into stuff like this and offer free consulting hours. We have to let the community (e.g., StackOverflow) help on these "how do I...?" questions (which is why the issue template says to ask those sorts of questions on StackOverflow).

@tillig tillig closed this as completed Feb 2, 2022
@tillig tillig added the question label Feb 2, 2022
@amunim
Copy link
Author

amunim commented Feb 2, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants