WebApplicationFactory 是集成测试的起点
它不是模拟容器,而是真正启动一个轻量级、内存内的 ASP.NET Core 应用实例,所有中间件、依赖注入、配置、路由都按真实逻辑运行——但不监听真实端口,也不依赖 IIS 或 Kestrel。这意味着你测的是“跑起来的程序”,不是一堆 Mock 对象拼凑的假象。
最简用法:
var factory = new WebApplicationFactory<program>()</program>(.NET 6+ 推荐指向
Program类;若用传统 Startup 模式,则指向
Startup)。注意:必须确保
TEntryPoint所在程序集能被当前测试项目引用,否则编译失败或运行时报
System.IO.FileNotFoundException。 不要在测试类构造函数里反复 new
WebApplicationFactory,它会创建独立应用上下文,开销大;应作为
[ClassFixture]复用 若测试中修改了配置(如数据库连接字符串),需通过
WithWebHostBuilder()覆盖,直接改
IConfiguration实例无效 .NET 7+ 中若项目启用了
ImplicitUsings,测试项目也需保持一致,否则可能找不到
WebApplicationFactory类型
HttpClient.GetFromJsonAsync 等扩展方法需要显式添加 System.Net.Http.Json
很多人写完
factory.CreateClient()后直接调
client.GetFromJsonAsync<user>("/api/users")</user>,结果编译报错:“GetFromJsonAsync未找到”。这不是 WebApplicationFactory 的问题,而是缺少 NuGet 包和 using。 必须安装
System.Net.Http.JsonNuGet 包(.NET 5+ 内置但需显式引用) 必须加
using System.Net.Http.Json;该扩展方法底层调用
SendAsync并自动反序列化,但不会自动处理 401/403 等非成功状态码——遇到时抛
HttpRequestException,需 try/catch 或用
EnsureSuccessStatusCode()若 API 返回
ProblemDetails(如启用
AddProblemDetails()),默认 JSON 反序列化会失败,建议用
GetAsync()+ 手动读取
response.Content.ReadAsStringAsync()再判断状态码
测试带认证的控制器要手动设置 Authorization Header 或注入 TestAuthHandler
WebApplicationFactory默认不带任何认证上下文。即使你在
Startup里配了 JWT Bearer 或 Cookie 认证,
CreateClient()发出的请求仍是匿名的。 最简单方式:给
HttpClient加 header:
client.DefaultRequestHeaders.Authorization = new("Bearer", "valid.jwt.token");token 可用 Microsoft.IdentityModel.Tokens.JwtSecurityTokenHandler在测试中快速生成 更可控方式:重写
ConfigureWebHost,用
services.AddAuthentication().AddScheme<authenticationschemeoptions testauthhandler>("TestScheme", _ => { })</authenticationschemeoptions>,并在测试中用 client.DefaultRequestHeaders.Authorization = new("TestScheme", "dummy")
切勿在测试中复用生产环境的密钥或签发服务——容易因时间偏移、密钥轮换导致 token 验证失败;用测试专用的 SymmetricSecurityKey就够了
数据库污染和事务回滚必须自己管理
WebApplicationFactory不会自动帮你清库或开事务。如果你的 API 写了数据库,每次测试跑完数据就留在那里,下次测试可能因主键冲突、唯一约束或脏数据失败。 推荐方案:用
Sqlite InMemory数据库(
UseSqlite("DataSource=:memory:")),并在每个测试前调 context.Database.OpenConnection(); context.Database.EnsureCreated();若必须用 SQL Server / PostgreSQL,可在
WithWebHostBuilder中替换
IDbContextFactory,让每次测试获取全新上下文,并在
Dispose时执行
context.Database.EnsureDeleted()别依赖
TransactionScope自动回滚——它在某些 EF Core 版本和数据库驱动下不生效,尤其跨线程或异步调用时 如果 API 内部用了
SaveChangesAsync以外的持久化方式(如直接调用 Dapper 或原始 ADO.NET),事务隔离必须手动包裹,
WebApplicationFactory完全不管这些
最难缠的其实是第三方服务耦合:邮件发送、消息队列、外部 HTTP 调用。它们不会因为你用了
WebApplicationFactory就自动变成可测的。得在
ConfigureWebHost里把对应服务替换成
Mock或内存实现,而且要确保替换时机早于控制器被解析——否则依赖注入容器已经缓存了真实实例,Mock 就白注册了。
