【问题标题】:How to switch services in TestServer using WebApplicationFactory?如何使用 WebApplicationFactory 在 TestServer 中切换服务?
【发布时间】:2019-05-09 13:45:18
【问题描述】:

我正在使用自定义 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);
    }

我想将我的默认IEmailProvider服务DefaultEmailProvider切换到我的特殊TestEmailProvider,但问题是方法ConfigureWebHostStartup.ConfigureServices(IServiceCollection services)之前执行,所以我的服务DefaultEmailProvider设置在之后TestEmailProvider。因此在我的ClientController 服务中使用DeafultEmailProvider 而不是测试服务。

我的问题是:

如何使用 WebApplicationFactory 将服务 DefaultEmailProvider 与我的 TestEmailProvider 切换?

@更新

好的,我已经设法更深入了。我发现builder.ConfigureTestServices() 方法覆盖了其他服务。但是,当在我的 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.'

@更新 28.05.2019

仍在寻找更好的解决方案,但我已经设法进行了 hack

在我的Startup.cs 中,我已经交换了我的

services.AddScoped&lt;IEmailProvider, SendGridEmailProvider&gt;();

使用 TryAdd[Something]

services.TryAddScoped&lt;IEmailProvider, SendGridEmailProvider&gt;();

【问题讨论】:

    标签: c# asp.net-core xunit


    【解决方案1】:

    您应该可以在 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);
        }
    }
    

    您可能想让自己更轻松,只需像上面那样实现工厂,而不是保持通用。我还为您的内存数据库添加了稍微不同的代码。

    它还没有被编译,但语法应该不会太远。 希望对您有所帮助。

    【讨论】:

      【解决方案2】:

      我只是花了几个小时尝试做类似的事情(在我的集成测试中覆盖服务)。结果ConfigureServices 运行之前 SUT(被测对象)服务配置。但是,ConfigureTestServices 之后运行,您可以在其中使用自己的 mock 覆盖。

      文档对此并不清楚,尽管我确实找到了这个:

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

      【讨论】:

        【解决方案3】:

        你可以根据环境来做。这只是为您指明正确方向的代码。

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

        我希望这会有所帮助。

        【讨论】:

        • 我想,但我不想用我的测试代码弄乱真实代码。
        • 那么你想达到什么目的?
        • 我想更改默认应用程序行为(可能会覆盖它)以使其适合集成测试。但我的应用代码库中不应该有任何与测试相关的东西。
        • 哦...它看起来像一个解决方案,有点。谢谢,但我正在寻找更流畅的东西。如果我没有找到。我可能会使用你的想法:)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-12-14
        • 1970-01-01
        • 2021-05-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多