laravel 5.5 单元测试必须继承 tests\testcase 才能启用服务容器、数据库事务回滚和 http 辅助方法;直接继承 phpunit\framework\testcase 会导致容器未启动、依赖解析失败、操作真实数据库等脱节问题。

单元测试在 Laravel 5.5 中不是“加个 phpunit 就完事”,它依赖框架层的隔离能力、容器行为和测试类继承链。直接用 PHPUnit\Framework\TestCase 写测试,大概率会漏掉服务容器自动解析、数据库事务回滚、HTTP 工具方法等关键支持——结果是测试看似通过,实际跟生产环境脱节。
为什么不能直接继承 PHPUnit\Framework\TestCase
Laravel 5.5 的测试基类 Tests\TestCase 不只是个空壳:它启动应用实例、注册服务提供者、配置测试数据库连接、启用事务式 DB 清理,并挂载了 $this->get()、$this->post() 等 HTTP 辅助方法。若跳过它,所有依赖容器注入的类(比如 UserRepository)将无法被自动解析,DB::table('users')->count() 也会操作真实数据库。
- 错误写法:
class UserTest extends \PHPUnit\Framework\TestCase→ 容器未启动,app(UserRepository::class)返回 null - 正确写法:
class UserTest extends \Tests\TestCase(注意命名空间) - 验证方式:在测试里调用
$this->app不报错,且config('app.debug')返回true
artisan make:test --unit 生成的测试文件为何仍需手动改写
php artisan make:test UserValidatorTest --unit 生成的文件默认继承 TestCase,但 Laravel 5.5 的 TestCase 默认启用数据库迁移和事务回滚——这对单元测试是过度的。单元测试不该碰数据库,否则违背“隔离外部依赖”原则,且拖慢执行速度。
- 必须删掉
use DatabaseMigrations;或use DatabaseTransactions;trait - 若测试中真要操作内存级数据,用
collect([ ... ])或新建对象,而非User::create(...) - 想验证 Eloquent 模型逻辑(如 accessor)?可以 new 实例后直接调方法,无需持久化
Mock 依赖时绕不开 Mockery 和容器绑定
Laravel 5.5 自带 Mockery,但光用 Mockery::mock() 不够——控制器或服务类里的依赖是通过容器解析的,你 mock 的实例必须“塞进容器”,否则构造函数注入或方法注入拿到的仍是真实实现。
- 正确姿势:
$mock = $this->mock(UserService::class);($this->mock()是TestCase提供的快捷方法,内部自动 bind 到容器) - 手动 bind 风险点:
$this->app->instance(UserService::class, $mock)可能被后续测试污染,推荐用$this->mock() - 别 mock 接口却忘记 bind:如果代码依赖
UserRepositoryInterface,mock 后必须$this->app->bind(UserRepositoryInterface::class, $mock)
测试失败时常见假阳性信号
有些测试“绿了”但毫无价值,典型表现是断言没覆盖真实逻辑路径,或误把框架默认行为当业务结果。
-
$this->assertTrue(true)这类断言永远过,但完全没测业务 - 测试
PriceCalculator时只测applyDiscount(100, 0.1)得到 90,却没覆盖边界值(如$discount = 1.0应返回 0) - 用
$this->assertEquals(200, $response->status())测 API,但没验证响应体结构或字段值,等于只测了路由是否注册
真正难的是让每个测试只做一件事,且这件事能被清晰反推到某行业务代码——比如 User::active()->count() 返回值变化,必须有对应测试显式覆盖 active 约束条件(status = 'active' 且 deleted_at is null)。











