PHPUnit测试ThinkPHP需手动初始化环境:引入base.php、定义路径、调用Bootstrap::init;事务须在setUp/tearDown中显式控制;Mock模型应返回Query实例;事件/验证器等需手动触发,否则覆盖率失真。

PHPUnit 测试类无法加载模型或 Db 类
ThinkPHP 的单元测试默认不加载应用环境,Db、Model 等类会报 Class 'think\Db' not found 或 Call to undefined method think\Model::where()。
必须手动初始化 ThinkPHP 的基础运行环境,否则所有框架能力都不可用:
- 在
tests/bootstrap.php中引入thinkphp/base.php(非start.php),避免启动完整 HTTP 生命周期 - 调用
think\initializer\Bootstrap::init()初始化核心服务容器 - 确保
APP_PATH和ROOT_PATH已正确定义,否则配置和模型路径解析失败
示例片段:
define('APP_PATH', __DIR__ . '/../app/');
define('ROOT_PATH', __DIR__ . '/../');
require __DIR__ . '/../vendor/topthink/thinkphp/base.php';
think\initializer\Bootstrap::init();
测试数据库事务回滚失败,脏数据残留
ThinkPHP 默认使用连接池 + 长连接,beginTransaction() 后若未显式 rollback(),下次测试可能读到上一轮写入的数据。
不能依赖 PHPUnit 的 @before/@after 注解自动清理,必须在每个测试方法内控制事务边界:
- 在
setUp()中调用Db::startTrans(),并在tearDown()中强制Db::rollback() - 避免在测试中使用
Db::table()->insert()等直写操作,优先走模型层,便于 mock 或替换连接 - 注意
Db::connect('test')显式指定测试连接,防止误操作生产配置
否则你会在 CI 环境里反复看到“唯一索引冲突”或“断言值不符”,但本地跑又正常。
Mock 模型方法时 getQuery() 报错或返回空对象
ThinkPHP 6+ 的模型方法如 where()、find() 返回的是链式查询对象,不是模型实例本身。直接 $mock->method('find')->willReturn(...) 很容易因返回类型不匹配导致后续调用崩溃。
关键点在于:mock 必须返回一个具备 getQuery() 方法的对象,且该方法要返回真实查询器实例:
- 用
Mockery::mock('think\db\Query')构造查询器 mock,再让模型方法返回它 - 不要 mock
Model::find(),而是 mockModel::db()->select()——因为底层实际调用的是查询器 - 若测试涉及软删除,需额外设置
withTrashed()或手动注入delete_time字段,否则find()永远为空
常见错误现象:Call to a member function getQuery() on null,本质是 mock 返回了 null 或普通数组,而非查询器对象。
测试覆盖率显示业务逻辑未执行,但断言通过
ThinkPHP 的事件监听、中间件、验证器等机制常在请求生命周期中隐式触发,而单元测试绕过了 HTTP 入口,这些逻辑根本不会运行。
比如你在控制器里写了 event('user_login', $user),但测试只调用 UserModel::login(),事件就不会触发——覆盖率工具自然标红那行 event()。
- 检查被测方法是否依赖
Request、Session、Cookie等上下文对象,它们在测试中默认为空 - 用
think\facade\Event::listen('user_login', function($u) { ... })在测试前注册监听,确认事件是否真的发出 - 验证器规则若写在控制器的
validate()中,需手动调用Validate::check(),不能指望模型 save() 自动触发
最常被忽略的是:你以为在测“登录逻辑”,其实只测了“密码比对”,真正的登录态写入、日志记录、风控检查全在线上才跑。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











