遵循官方 MS 集成测试 .Net Core.
我能够完成集成测试的第一部分,其中我没有覆盖我正在测试的应用程序的启动类(即我使用的 Web 应用程序工厂没有覆盖任何服务)。
我想覆盖数据库设置以使用内存数据库进行集成测试。我遇到的问题是配置继续尝试使用 sql server
services.AddHangfire()
。
如何在集成测试中仅覆盖上述特定项目?我只想覆盖
AddHangfire
设置,而不是 services.AddScoped<ISendEmail, SendEmail>().
任何帮助将不胜感激。
使用自定义 Web 应用程序工厂测试类
public class HomeControllerShouldCustomFactory : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public HomeControllerShouldCustomFactory(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async Task IndexRendersCorrectTitle()
{
var response = await _client.GetAsync("/Home/Index");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("Send Email", responseString);
}
}
自定义 Web 应用程序工厂
public class CustomWebApplicationFactory<TStartup>: WebApplicationFactory<SendGridExample.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
services.AddHangfire(x => x.UseStorage(inMemory));
// Build the service provider.
var sp = services.BuildServiceProvider();
});
}
}
我正在测试的应用程序中的startup.cs
public IConfiguration 配置 { get; } 公共 IHostingEnvironment 环境 { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped<ISendEmail, SendEmail>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHangfireServer();
app.UseHangfireDashboard();
RecurringJob.AddOrUpdate<ISendEmail>((email) => email.SendReminder(), Cron.Daily);
app.UseMvc();
更新
我在其他仅使用实体框架的示例项目中没有看到这个问题。我有一个简单的应用程序,带有使用 SQL Server 的应用程序数据库上下文。在我的测试类中,我用内存数据库覆盖它,一切正常。我不明白为什么它可以在我的示例应用程序中工作,但不能在我的主应用程序中工作。这与 HangFire 的工作原理有关吗?
在我的测试应用程序(下面的示例代码)中,我可以删除我的 sql 数据库,运行我的测试,并且测试通过,因为应用程序数据库上下文不会查找 sql server 实例,而是使用内存数据库。在我的应用程序中,HangFire 服务不断尝试使用 sql server 数据库(如果我删除数据库并尝试使用内存数据库进行测试 - 它会失败,因为它找不到尝试连接的实例) 。当两个项目使用相似的路径时,为什么两个项目的工作方式会有如此巨大的差异?
我运行了集成测试的调试器,它调用上面的家庭控制器上的索引方法(使用 CustomWebApplicationFactory)。当我初始化测试服务器时,它会通过我的启动类,该类在下面的配置服务中调用:
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
之后,Configure 方法尝试调用以下语句:
app.UseHangfireServer();
此时测试失败,因为找不到数据库。该数据库托管在 Azure 上,因此我尝试将其替换为内存服务器以进行某些集成测试。我采取的方法不正确吗?
我的示例应用程序正在运行
我的示例应用程序中的应用程序数据库上下文
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public virtual DbSet<Message> Messages { get; set; }
public async Task<List<Message>> GetMessagesAsync()
{
return await Messages
.OrderBy(message => message.Text)
.AsNoTracking()
.ToListAsync();
}
public void Initialize()
{
Messages.AddRange(GetSeedingMessages());
SaveChanges();
}
public static List<Message> GetSeedingMessages()
{
return new List<Message>()
{
new Message(){ Text = "You're standing on my scarf." },
new Message(){ Text = "Would you like a jelly baby?" },
new Message(){ Text = "To the rational mind, nothing is inexplicable; only unexplained." }
};
}
}
我的示例应用程序中的Startup.cs
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
CustomWebApplicationFactory - 在我的单元测试项目中
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
// Add a database context (ApplicationDbContext) using an in-memory
// database for testing.
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseInMemoryDatabase("InMemoryDbForTesting");
options.UseInternalServiceProvider(serviceProvider);
});
// Build the service provider.
var sp = services.BuildServiceProvider();
});
}
}
我的单元测试项目中的单元测试
public class UnitTest1 : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<Startup> _factory;
public UnitTest1(CustomWebApplicationFactory<Startup> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
[Fact]
public async System.Threading.Tasks.Task Test1Async()
{
var response = await _client.GetAsync("/");
//response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
Assert.Contains("Home", responseString);
}
更新2
我想我找到了尝试覆盖集成测试类中所有配置的替代方法。由于覆盖 HangFire 比覆盖 ApplicationDBContext 要复杂得多,我想出了以下方法:
Startup.cs
if (Environment.IsDevelopment())
{
var inMemory = GlobalConfiguration.Configuration.UseMemoryStorage();
services.AddHangfire(x => x.UseStorage(inMemory));
}
else
{
services.AddHangfire(x => x.UseSqlServerStorage(Configuration["DBConnection"]));
}
然后在我的 CustomWebApplicationBuilder 中,我重写环境类型以进行测试:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<SendGridExample.Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development"); //change to Production for alternate test
builder.ConfigureServices(services =>
{
// Create a new service provider.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkInMemoryDatabase()
.BuildServiceProvider();
});
}
}
通过这种方法,我不需要担心必须执行额外的逻辑来满足hangfire 对活动数据库的检查。它有效,但我并不 100% 相信它是最好的方法,因为我在我的生产启动类中引入了分支。
您需要检查两种不同的情况。
BackgroundJob
IBackgroundJobClient
对于第一个选项,您无法将
SqlServerStorage
替换为 MemoryStorage
。
对于
UseSqlServerStorage
,它将按JobStorage
重置SqlServerStorage
。
public static IGlobalConfiguration<SqlServerStorage> UseSqlServerStorage(
[NotNull] this IGlobalConfiguration configuration,
[NotNull] string nameOrConnectionString)
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (nameOrConnectionString == null) throw new ArgumentNullException(nameof(nameOrConnectionString));
var storage = new SqlServerStorage(nameOrConnectionString);
return configuration.UseStorage(storage);
}
UseStorage
public static class GlobalConfigurationExtensions
{
public static IGlobalConfiguration<TStorage> UseStorage<TStorage>(
[NotNull] this IGlobalConfiguration configuration,
[NotNull] TStorage storage)
where TStorage : JobStorage
{
if (configuration == null) throw new ArgumentNullException(nameof(configuration));
if (storage == null) throw new ArgumentNullException(nameof(storage));
return configuration.Use(storage, x => JobStorage.Current = x);
}
这意味着,无论您在
CustomWebApplicationFactory
中设置什么,UseSqlServerStorage
都会用BackgroundJob
重置SqlServerStorage
。
对于第二个选项,可以将
IBackgroundJobClient
替换为 MemoryStorage
by
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.AddSingleton<JobStorage>(x =>
{
return GlobalConfiguration.Configuration.UseMemoryStorage();
});
});
}
}
总之,我建议您注册
IBackgroundJobClient
并尝试第二个选项来实现您的要求。
更新1
对于DB is not available,无法通过配置依赖注入来解决。这个错误是由调用
services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("ASP_NetPractice")));
引起的。
要解决此错误,您需要覆盖
Startup.cs
中的此代码。
尝试以下步骤:
将
Startup
更改为以下:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Rest Code
ConfigureHangfire(services);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//Rest Code
app.UseHangfireServer();
RecurringJob.AddOrUpdate(() => Console.WriteLine("RecurringJob!"), Cron.Minutely);
}
protected virtual void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(config =>
config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"))
);
}
}
在测试项目中创建
StartupTest
。
public class StartupTest : Startup
{
public StartupTest(IConfiguration configuration) :base(configuration)
{
}
protected override void ConfigureHangfire(IServiceCollection services)
{
services.AddHangfire(x => x.UseMemoryStorage());
}
}
CustomWebApplicationFactory
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint> where TEntryPoint: class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost.CreateDefaultBuilder(null)
.UseStartup<TEntryPoint>();
}
}
测试
public class HangfireStorageStartupTest : IClassFixture<CustomWebApplicationFactory<StartupTest>>
{
private readonly HttpClient _client;
private readonly CustomWebApplicationFactory<StartupTest> _factory;
public HangfireStorageStartupTest(CustomWebApplicationFactory<StartupTest> factory)
{
_factory = factory;
_client = factory.CreateClient();
}
}