WebApplicationFactory 是轻量级测试宿主,勿在每个测试中 new 实例、勿依赖生产环境认证/配置、勿测前端交互;需匹配程序集引用与入口类型(Program/Startup),同步隐式 using,安装 System.Net.Http.Json 并 using 命名空间,注意 GetFromJsonAsync 不检查状态码,授权测试需手动设 Authorization header,内存数据库等依赖须在 ConfigureWebHost 中替换服务。

WebApplicationFactory不是“教程型工具”,它是个轻量级测试宿主,用错姿势会拖慢 CI、污染状态、甚至让测试结果不可靠。直接上关键判断:**别在每个测试方法里 new 它,别指望它自动继承生产环境的认证或配置,也别用它测前端交互**。
创建 WebApplicationFactory 实例时,程序集引用和入口类型必须匹配
常见错误是 new WebApplicationFactory 编译失败或运行时报 System.IO.FileNotFoundException。原因通常是测试项目没正确引用被测项目的输出程序集——不是 NuGet 包,而是项目引用(dotnet add reference ../YourApp/YourApp.csproj)。
.NET 6+ 默认用 Program 类作为入口,.NET 5 及之前用 Startup。若混用(比如测试项目 TargetFramework 是 net6.0,但被测项目是 net5.0 + Startup),WebApplicationFactory 会找不到类型符号。
隐式 using(ImplicitUsings)也要同步:如果被测项目启用了 ,测试项目也得开,否则编译器可能认不出 WebApplicationFactory。
HttpClient.GetFromJsonAsync 报“未找到该扩展方法”?缺包 + 缺 using
写完 factory.CreateClient() 后直接链式调用 client.GetFromJsonAsync,编译器报错,不是 WebApplicationFactory 的锅。
- 必须安装 NuGet 包:
System.Net.Http.Json - 必须加命名空间:
using System.Net.Http.Json;
这个扩展方法底层调用 SendAsync 并自动反序列化,但它**不检查 HTTP 状态码**:遇到 401/403 会直接抛 HttpRequestException,而不是返回 null 或默认值。所以别裸调,加 EnsureSuccessStatusCode() 或 try/catch。
如果 API 启用了 AddProblemDetails()(比如全局异常中间件),默认反序列化会失败——因为响应体是 ProblemDetails 结构,不是你的 User 类型。此时建议改用 client.GetAsync("/api/users"),再手动读 response.Content.ReadAsStringAsync() 判断状态码和内容。
测试带 [Authorize] 的控制器,WebApplicationFactory 默认不带登录态
WebApplicationFactory 创建的 HttpClient 是匿名请求,哪怕你在 Program.cs 里配了 JWT Bearer 或 Cookie 认证,它也不会自动携带 token。
最简方案是手动塞 header:
var client = factory.CreateClient();
client.DefaultRequestHeaders.Authorization = new("Bearer", "valid.jwt.token");
token 可用 JwtSecurityTokenHandler 在测试中快速生成(注意设置好 issuer、audience、signing key 和有效期),不用依赖真实 IdP。不要复用同一个 token 跨测试——签名密钥或过期时间出问题会导致偶发失败。
更彻底的方式是自定义 TestAuthHandler 注入到测试服务容器里,但多数场景没必要。记住:认证逻辑本身应单独单元测试,集成测试只验证「有合法 token 时能访问,没 token 时被拒」。
想换内存数据库?ConfigureWebHost 里覆盖 AddDbContext 才有效
直接改 IConfiguration 或在测试方法里调 services.Replace(...) 没用——WebApplicationFactory 的服务注册发生在构建阶段,且顺序固定:先执行被测项目的 Program.cs,再执行你通过 WithWebHostBuilder 或重写 ConfigureWebHost 注入的逻辑。
正确做法是在工厂类里重写 ConfigureWebHost:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
services.RemoveAll(typeof(DbContextOptions<appdbcontext>));
services.AddDbContext<appdbcontext>(options =>
options.UseInMemoryDatabase("TestDb"));
});
}
</appdbcontext></appdbcontext>
注意 RemoveAll 很关键:避免 EF Core 尝试同时注册 SQL Server 和 InMemory 提供程序导致冲突。另外,InMemoryDatabase 名称要唯一(比如加 Guid),否则多个测试并行跑会互相干扰。
Hangfire、Redis Client 等第三方服务同理:必须在 ConfigureWebHost 阶段替换,不能靠修改 appsettings.Test.json —— 那些配置项通常在 Program.cs 里就被读走了。
真正的难点不在怎么写,而在于**边界意识**:它测的是「请求进来、中间件流转、控制器执行、响应出去」这一整条通路,但绝不负责验证 JS 是否渲染正确、CSS 是否生效、或者浏览器是否触发了某个事件。那些交给 Playwright;这里只管 HTTP 层契约。











