How to overwrite a scoped service with a decorated implementation?

后端 未结 5 576
日久生厌
日久生厌 2020-12-18 06:07

I\'m trying to write an ASP.NET Core 2.2 integration test, where the test setup decorates a specific service that would normally be available to the API as a dependency. The

5条回答
  •  伪装坚强ぢ
    2020-12-18 06:46

    There's actually a few things here. First, when you register a service with an interface, you can only inject that interface. You are in fact saying: "when you see IBarService inject an instance of BarService". The service collection doesn't know anything about BarService itself, so you cannot inject BarService directly.

    Which leads to the second issue. When you add your new DecoratedBarService registration, you now have two registered implementations for IBarService. There's no way for it to know which to actually inject in place of IBarService, so again: failure. Some DI containers have specialized functionality for this type of scenario, allowing you to specify when to inject which, Microsoft.Extensions.DependencyInjection does not. If you truly need this functionality, you can use a more advanced DI container instead, but considering this is only for testing, that would like be a mistake.

    Third, you have a bit of a circular dependency here, as DecoratedBarService itself takes a dependency on IBarService. Again, a more advanced DI container can handle this sort of thing; Microsoft.Extensions.DependencyInjection cannot.

    Your best bet here is to use an inherited TestStartup class and factor out this dependency registration into a protected virtual method you can override. In your Startup class:

    protected virtual void AddBarService(IServiceCollection services)
    {
        services.AddScoped();
    }
    

    Then, where you were doing the registration, call this method instead:

    AddBarService(services);
    

    Next, in your test project create a TestStartup and inherit from your SUT project's Startup. Override this method there:

    public class TestStartup : Startup
    {
        protected override void AddBarService(IServiceCollection services)
        {
            services.AddScoped(_ => new DecoratedBarService(new BarService()));
        }
    }
    

    If you need to get dependencies in order to new up any of these classes, then you can use the passed in IServiceProvider instance:

    services.AddScoped(p =>
    {
        var dep = p.GetRequiredService();
        return new DecoratedBarService(new BarService(dep));
    }
    

    Finally, tell your WebApplicationFactory to use this TestStartup class. This will need to be done via the UseStartup method of the builder, not the generic type param of WebApplicationFactory. That generic type param corresponds to the entry point of the application (i.e. your SUT), not which startup class is actually used.

    builder.UseStartup();
    

提交回复
热议问题