hyperf单元测试必须手动启动协程调度器并设置hook,避免dbconnection复用、正确mock协程函数、防止超时阻塞及资源泄漏。

Hyperf 单元测试必须启动协程调度器
不手动启动 Swoole\Coroutine::set 或调用 Co::set,直接跑 go、Co::sleep 会报错或静默失败。Hyperf 的测试基类 TestCase 默认不开启协程环境,哪怕你用了 @runInSeparateProcess 也没用。
实操建议:
- 在
setUp()中显式调用Co::set(['hook_flags' => SWOOLE_HOOK_ALL]),确保协程 Hook 生效 - 若测试中需并发协程(如模拟多个请求),用
go(function () { ... })启动,但记得加Co::wait()等待完成,否则测试可能提前退出 - 避免在
tearDown()里调用Co::getPcid()判定协程 ID —— 测试进程退出后协程已销毁,返回false会导致误判
DbConnection 在协程中复用会出错
Hyperf 默认的 DbConnection 实例不是协程安全的,直接在多个 go 里共用同一个连接对象,会出现 MySQL server has gone away 或查询结果错乱。
实操建议:
- 每个协程内通过
$this->db->connection()新建连接实例,而不是复用$this->db属性 - 若使用
Model,确保模型配置了'pool' => ['min_connections' => 1],否则协程间可能抢到同一连接 - 测试完主动调用
$connection->close(),防止连接泄漏影响后续用例
Mock 协程函数要绕过 Swoole Hook
想 mock Co::sleep 或 Co::httpGet?直接用 PHPUnit 的 static::createMock() 不生效 —— 这些是全局函数,且已被 Swoole Hook 覆盖,mock 对象根本不会被调用。
实操建议:
- 改用依赖注入:把协程调用封装进服务类,测试时替换该服务的 mock 实例
- 对简单场景,用
putenv('SWOOLE_HOOK_FLAGS=0')临时关闭 Hook,再用function_exists('Co::sleep')判定是否降级为usleep,便于断言 - 避免 mock
go本身 —— 它只是调度入口,真正逻辑在闭包里,mock 闭包参数或返回值更可靠
测试超时容易被协程阻塞掩盖
PHPUnit 的 @timeout 注解对协程无效。一个 Co::sleep(10) 会让整个测试卡死 10 秒,而不是抛出超时异常。
实操建议:
- 禁用
@timeout,改用Co::create(function () { ... }, $timeout)包裹关键协程逻辑 - 在
setUp()中设置Co::set(['max_coroutine' => 1000]),防止单测耗尽协程数导致后续用例 hang 住 - CI 环境务必加
ulimit -n 65535,否则高频协程测试会因文件描述符不足直接崩溃











