c# - 如何使用 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>();
解决方案
您应该可以在 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);
}
}
您可能想让自己更轻松,只需像上面那样实现工厂,而不是保持通用。我还为您的内存数据库添加了稍微不同的代码。
它还没有被编译,但语法应该不会太远。希望有帮助。
推荐阅读
- r - R中的Toeplitz矩阵函数
- javascript - React 功能组件获取用户数据不更新
- linux - 如何自动记录用户在 bash 中运行的所有命令所花费的时间
- windows - Windows命令批处理文件从嵌套循环中的字符串调用变量
- jira - 在 Jira 中使用正确的问题类型
- javascript - 将 Javascript 列表转换为列表列表
- arrays - 查找最大索引 j 的有效算法,使得从索引 i 到 j 的总和小于 k
- reactjs - 如何使用 APM React 监控用户点击
- wordpress - 在元框中使用 wp_editor tinyMCE 会导致离开页面时出现表单警报
- python - 如果Django中的相同值很少,如何过滤查询值