简体   繁体   English

如何使用 WebApplicationFactory 在 TestServer 中切换服务?

[英]How to switch services in TestServer using WebApplicationFactory?

I'm using custom WebApplication factory我正在使用自定义 WebApplication 工厂

public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup: class {
    protected override void ConfigureWebHost(IWebHostBuilder builder) {
        builder.ConfigureServices(services => {
            // Create a new service provider.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            services.AddDbContext<GrabGoContext>(options => {
                options.UseInMemoryDatabase("GrabGoDb");
                options.UseInternalServiceProvider(serviceProvider);
            });

            services.AddSingleton<TestEmailServer>();
            services.AddScoped<IEmailProvider, TestEmailProvider>(); // <- HERE
        });
        base.ConfigureWebHost(builder);
    }

I want to switch my default IEmailProvider service called DefaultEmailProvider to my special TestEmailProvider , but the problem is that the method ConfigureWebHost is executed before Startup.ConfigureServices(IServiceCollection services) , so my service DefaultEmailProvider is set after TestEmailProvider .我想将名为DefaultEmailProvider的默认IEmailProvider服务切换到我的特殊TestEmailProvider ,但问题是方法ConfigureWebHost是在Startup.ConfigureServices(IServiceCollection services)之前执行的,所以我的服务DefaultEmailProvider是在TestEmailProvider之后设置的。 Therefore in my ClientController service DeafultEmailProvider is used instead of test service.因此,在我的ClientController服务中,使用了DeafultEmailProvider而不是测试服务。

My question is:我的问题是:

How can I switch service DefaultEmailProvider with my TestEmailProvider using WebApplicationFactory?如何使用 WebApplicationFactory 将服务DefaultEmailProvider与我的TestEmailProvider切换?

@Update @更新

Ok, I've managed to go deeper.好的,我已经设法深入 go。 I found that method builder.ConfigureTestServices() overrides other services.我发现方法builder.ConfigureTestServices()覆盖了其他服务。 But when switch this in my ConfigureWebHost(IWebHostBuilder builder) method and I try to create new HttpClient using CreateClient() it throws:但是,当在我的ConfigureWebHost(IWebHostBuilder builder)方法中切换它并尝试使用CreateClient()创建新的 HttpClient 时,它会抛出:

System.InvalidOperationException: 'A ConfigureServices method that returns an IServiceProvider is not compatible with the use of one or more IStartupConfigureServicesFilter. Use a void returning ConfigureServices method instead or a ConfigureContainer method.'

@Update 28.05.2019 @更新 28.05.2019

Still looking for better solution , but I've manage to do sort of hack .仍在寻找更好的解决方案,但我已经设法做到了。

In my Startup.cs I've swapped my在我的Startup.cs中,我交换了我的

services.AddScoped<IEmailProvider, SendGridEmailProvider>();

with TryAdd[Something]与 TryAdd[某事]

services.TryAddScoped<IEmailProvider, SendGridEmailProvider>();

You should be able to do it in the ConfigureWebHost similar to the below:您应该能够在类似于以下内容的 ConfigureWebHost 中执行此操作:

public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder) 
    {
        builder.ConfigureServices(services => 
        {
            // Remove application IEmailProvider service
            var emailProviderDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IEmailProvider));
            if (emailProviderDescriptor != null)
            {
                services.Remove(emailProviderDescriptor);
            }

            // Add new test service(s)
            services.AddScoped<IEmailProvider, TestEmailProvider>();
            services.AddSingleton<TestEmailServer>(); // May need to remove using the descriptor similar to IEmailProvider

            // Remove the app's GrabGoContext registration, add in memory one then seed data.
            var dbDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<GrabGoContext>));
            if (dbDescriptor != null)
            {
                services.Remove(dbDescriptor);
            }

            services.AddDbContext<GrabGoContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryDbForTesting");
            });

            var serviceProvider = services.BuildServiceProvider();
            using var scope = serviceProvider.CreateScope();
            var scopedServices = scope.ServiceProvider;
            var grabGoContext = scopedServices.GetRequiredService<GrabGoContext>();

            // Seed your db here & save..
            grabGoContext.SaveChanges();
        });

        base.ConfigureWebHost(builder);
    }
}

You may want to make it easier on yourself and just implement the factory, like the above, rather than keep it generic.您可能想让自己更轻松,只需实现工厂,就像上面一样,而不是保持通用。 I've also added slightly different code for your in-memory database.我还为您的内存数据库添加了稍微不同的代码。

It hasn't been compiled, but the syntax shouldn't be far off.它尚未编译,但语法应该不远。 Hope that helps.希望有帮助。

You can do it based on Environment.您可以根据环境进行操作。 This is just code to point you in the right direction.这只是为您指明正确方向的代码。

  public IHostingEnvironment HostingEnvironment { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        if (HostingEnvironment.IsDevelopment())
        {
            services.AddTransient<ITestService, TestService>();
        }
        else
        {
            services.AddTransient<IRealService, RealService>();
        }

        // other services
    }

I hope this helps.我希望这有帮助。

I just spent several hours trying to do a similar thing (override the services in my integration tests).我只是花了几个小时试图做类似的事情(覆盖我的集成测试中的服务)。 Turns out ConfigureServices runs before the SUT (subject under test) service configuration.结果是ConfigureServicesSUT(被测对象)服务配置之前运行。 ConfigureTestServices , however, runs after and is where you would override with your mocks.但是, ConfigureTestServices之后运行并且是您可以用模拟覆盖的地方。

The docs are not at all clear on this, though I did, after the fact, find this:文档对此一点也不明确,尽管我确实在事后找到了这一点:

https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.0#inject-mock-services https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-3.0#inject-mock-services

You can try this, call ConfigureTestServices rather than call ConfigureServices :你可以试试这个,调用ConfigureTestServices而不是调用ConfigureServices

protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder
            .UseEnvironment("Test")
            .ConfigureTestServices(services =>
            {
                  // Put your logic here
            }); ;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用 WebApplicationFactory 禁用服务 - How to disable services with WebApplicationFactory 如何使用 WebApplicationFactory 播种测试数据? - How to seed test data using WebApplicationFactory? 如何在 WebApplicationFactory 中设置 SQLite? - How to setup SQLite in WebApplicationFactory? 网络核心:从WebApplicationFactory查找服务 - Net Core: Find Services from WebApplicationFactory 将CefSharp与Owin TestServer一起使用 - Using CefSharp with Owin TestServer 使用TestServer处理错误情况 - Using TestServer for error scenarios 如何从 WebApplicationFactory 获取配置? - How to get Configuration from WebApplicationFactory? 如何使用WebApplicationFactory在xUnit集成测试中设置IdentityServer4 BackChannelHandler? - How can I set my IdentityServer4 BackChannelHandler from within an xUnit integration test using WebApplicationFactory? 将HttpClientHandler.AutomaticDecompression与WebApplicationFactory.CreateClient()一起使用 - Using HttpClientHandler.AutomaticDecompression with WebApplicationFactory.CreateClient() 如何使用TestServer和Antiforgery修复POST集成测试中的500 Internal Server Error? ASP.NET核心 - How to fix 500 Internal Server Error for POST integration tests using TestServer and Antiforgery? ASP.NET Core
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM