首页 > 解决方案 > 如何使用 WebApplicationFactory 在 TestServer 中切换服务?

问题描述

我正在使用自定义 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,但问题是该方法ConfigureWebHost是在之前执行的Startup.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

仍在寻找更好的解决方案,但我已经设法做到

在我的Startup.cs我交换了我的

services.AddScoped<IEmailProvider, SendGridEmailProvider>();

与 TryAdd[某事]

services.TryAddScoped<IEmailProvider, SendGridEmailProvider>();

标签: c#asp.net-corexunit

解决方案


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

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

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


推荐阅读