.NET Core单元测试:CustomWebApplicationFactory多类并行运行异常求助
这个问题我太熟悉了——当多个测试类共用同一个CustomWebApplicationFactory时,并行运行很容易因为共享静态状态、数据库连接或者其他全局资源崩溃。下面给你几个按优先级排序的解决方案,从根源解决到临时规避都有:
1. 为每个测试类创建独立的Factory子类
这是最稳妥的方式,让每个测试类拥有完全独立的Web应用实例,彻底隔离所有状态。
具体做法:
给每个测试类写一个专属的CustomWebApplicationFactory子类,哪怕只是空实现也没关系:
// 给Test1用的专属Factory public class Test1WebApplicationFactory : CustomWebApplicationFactory<Startup> { // 这里可以加Test1特有的配置,比如替换特定服务、设置环境变量 protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); // 比如给Test1用测试环境的特殊配置 builder.UseEnvironment("Test1"); } } // 给Test2用的专属Factory public class Test2WebApplicationFactory : CustomWebApplicationFactory<Startup> { protected override void ConfigureWebHost(IWebHostBuilder builder) { base.ConfigureWebHost(builder); builder.UseEnvironment("Test2"); } }
然后在测试类里分别注入对应的Factory:
public class Test1 : IClassFixture<Test1WebApplicationFactory> { private readonly HttpClient _client; public Test1(Test1WebApplicationFactory factory) { _client = factory.CreateClient(); } // 你的测试方法... } public class Test2 : IClassFixture<Test2WebApplicationFactory> { private readonly HttpClient _client; public Test2(Test2WebApplicationFactory factory) { _client = factory.CreateClient(); } // 你的测试方法... }
每个子类Factory会启动独立的Web Host,完全不会共享任何资源。
2. 重置静态状态+隔离共享资源
如果不想写多个Factory子类,可以在原Factory里做状态隔离,重点处理静态字段和共享资源(比如数据库)。
重置静态状态:
在Factory的ConfigureWebHost方法里,手动重置所有你代码里的静态类状态:
public class CustomWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class { protected override void ConfigureWebHost(IWebHostBuilder builder) { // 重置所有自定义静态类的状态 MyGlobalSettings.Reset(); MyStaticCache.Clear(); builder.ConfigureServices(services => { // 其他服务配置 }); } }
隔离数据库资源:
如果用的是内存数据库,一定要给每个Factory实例生成唯一的数据库名称,避免不同测试类读写同一份数据:
protected override void ConfigureWebHost(IWebHostBuilder builder) { var uniqueDbName = $"TestDb_{Guid.NewGuid()}"; builder.ConfigureServices(services => { // 先移除原来的DbContext注册 var dbDescriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions<MyDbContext>)); if (dbDescriptor != null) { services.Remove(dbDescriptor); } // 添加独立的内存数据库 services.AddDbContext<MyDbContext>(options => { options.UseInMemoryDatabase(uniqueDbName); }); }); }
3. 让测试在独立进程运行
如果遇到第三方库的静态字段没法重置的情况,就只能用终极方案:让每个测试类在单独的进程里跑,彻底隔离进程内的所有共享状态。
配置方法:
在你的测试项目根目录创建xunit.runner.json文件,内容如下:
{ "parallelizeTestCollections": true, "runConfiguration": { "environmentVariables": { "XUNIT_INDEPENDENT_PROCESSES": "true" } } }
记得把这个文件的“复制到输出目录”设置为“如果较新则复制”,这样xUnit会为每个测试类启动单独的进程,完全不会有共享状态问题——缺点是测试启动速度会慢一点,但胜在彻底解决问题。
4. 重构代码,干掉静态状态
从根源解决问题的话,最好把代码里的静态字段、静态服务都改成依赖注入的实例服务。比如把:
public static class MyStaticService { public static int RequestCounter { get; set; } }
改成可注入的服务:
public interface IRequestCounter { int Count { get; set; } } public class RequestCounter : IRequestCounter { public int Count { get; set; } }
然后在Startup里注册为Scoped或Transient:
services.AddScoped<IRequestCounter, RequestCounter>();
这样每个Web Host实例都会有自己的服务实例,再也不会出现并行测试冲突了。
内容的提问来源于stack exchange,提问作者Luka

