测试跑起来只需三步:写测试类、继承tests\testcase、运行php artisan test;卡点几乎全是环境配置和上下文缺失,如csrf未禁用、app_key缺失、db_connection未设为:memory:、refreshdatabase未启用或误用裸phpunit基类。

直接写测试类、继承 Tests\TestCase、用 php artisan test 运行——这三步走通了,测试就算跑起来了;卡住的几乎全是环境配置和上下文缺失问题,不是语法不会写。
为什么 $this->postJson() 一跑就 500 或 419
这不是你路由或控制器写错了,而是 Laravel 测试环境默认禁用 CSRF 验证,但某些配置会把它“意外打开”。
- 检查
app/Http/Kernel.php中web或api中间件组是否误加了VerifyCsrfToken;Laravel 默认已通过WithoutMiddlewaretrait 关闭它,加了就冲突 -
phpunit.xml里必须有<env name="APP_ENV" value="testing"></env>,否则 .env 不加载,APP_KEY缺失会导致加密失败,直接 500 - 数据库表不存在也会 500:确认
RefreshDatabasetrait 已启用(tests/TestCase.php默认配好),且phpunit.xml中DB_CONNECTION=sqlite、DB_DATABASE=:memory: - 别在测试里手动调
Artisan::call('migrate')——RefreshDatabase会自动处理,重复执行反而可能报错
Model 单元测试里 save() 不触发事件或软删除
现象是 $user->delete() 后查库发现 deleted_at 为空,或者 creating 观察者根本没进。根本原因不是模型写得不对,而是测试没挂上 Laravel 的应用上下文。
- 确保测试类继承自
Tests\TestCase,而不是裸写的PHPUnit\Framework\TestCase - 检查
tests/Pest.php(如果用了 Pest)或测试类顶部是否有uses(Tests\TestCase::class);没有这句,服务容器、事件监听器、软删除 trait 全部不注册 - Model 测试建议放
tests/Unit,但必须显式 useRefreshDatabase或DatabaseTransactionstrait;仅靠TestCase基类不够,事务机制要手动激活 -
assertDatabaseMissing('users', ['id' => 1])失败?说明事务没生效——先确认 trait 是否真被引入,再看phpunit.xml是否把:memory:当成 MySQL 连接用了
Http::fake() 拦不住请求,还是发出去了
Http::fake() 只拦截通过 Laravel Http facade 发出的请求,对 file_get_contents、new GuzzleHttp\Client()、Stripe SDK 等完全无效。
- 先确认被测代码真正在用
Http::get()或Http::post();如果用了第三方 SDK,得换方案:比如对 Guzzle,mock 它的HandlerStack,而不是指望Http::fake() - 通配符路径要写全,例如
Http::fake(['https://api.example.com/v1/*' => Http::response([])]);只写api.example.com不匹配带路径的请求 - 多次请求同一 URL 时,用闭包形式 fake 才能返回不同响应:
Http::fake(['*'=> fn() => Http::response(['id'=>1])]) - 别在测试中混用
Http::fake()和Mockery::mock()——前者轻量精准,后者容易漏掉响应结构或 headers
测试跑完数据库没清空,或者 factory 数据“消失”了
这不是 RefreshDatabase 失效,而是你没搞清它的作用边界:它只重置迁移建的表结构 + 回滚事务,factory 写入的数据本就会被事务回滚掉,而 migration 是在事务外执行的。
-
:memory:SQLite 最快,但不支持外键约束;如果你模型用了foreignId('user_id')->constrained(),得切到文件 SQLite 或 MySQL 测试库 - MySQL 测试库必须提前创建,且
phpunit.xml里明确配<env name="DB_DATABASE" value="laravel_test"></env>;别让它默认连本地homestead库 - 别在测试里写
DB::table('users')->truncate()——它绕过 Eloquent 事件,也破坏RefreshDatabase的事务隔离机制 - Factory 数据“不见了”是正常现象:事务一回滚,数据就没了;要验证创建逻辑,用
$user = User::factory()->create(); $this->assertNotNull($user->fresh());就够了
最常被忽略的点是:所有测试行为都依赖 phpunit.xml 的环境变量配置,而不是 .env;哪怕只改了一个 DB_DATABASE,也得去 XML 里动,否则测试永远在“猜”你想要什么环境。











