Hyperf单元测试需继承HyperfTest\TestCase而非PHPUnit原生类,因其自动管理协程环境;仅Runtime::enableCoroutine()不足,因PHPUnit同步执行导致协程未调度或失效。

Hyperf 的单元测试默认不启用协程环境,直接跑 go 或 Co::sleep 会报错或静默失败——必须显式启动协程调度器并配置好 hyperf/testing 的协程支持。
为什么 test/bootstrap.php 中的 Runtime::enableCoroutine() 不够用
仅调用 Runtime::enableCoroutine() 只是开启了协程能力,但 PHPUnit 进程仍是同步执行,go 启动的协程在测试结束时可能已被销毁,导致:
-
Swoole\Coroutine::stats()显示协程数为 0,实际协程未真正调度 -
Co::sleep(0.1)阻塞主线程而非让出控制权,测试超时 - 依赖
ApplicationContext::getContainer()的协程组件(如Db、Redis)抛出Coroutine not available
根本原因是:Hyperf 的协程运行时需搭配 Swoole\Runtime::enableCoroutine() + 正确的 PHPUnit 执行上下文,而 hyperf/testing 提供了封装好的 TestCase 基类来接管这个流程。
正确继承 HyperfTest 并启用协程调度
不用自己手动启停协程,直接使用 hyperf/testing 提供的基类即可。它会在每个测试方法前后自动管理协程环境:
- 继承
\HyperfTest\TestCase(不是PHPUnit\Framework\TestCase) - 确保
phpunit.xml中bootstrap指向test/bootstrap.php,且其中已加载Hyperf\Testing\Bootstrap - 测试方法内可安全使用
go、Co::sleep、Db::query()等协程操作
示例:
use HyperfTest\TestCase;
class CoroutineTest extends TestCase
{
public function test_can_run_coroutine_in_test()
{
$result = null;
go(function () use (&$result) {
Co::sleep(0.01);
$result = 'done';
});
// 等待协程完成(HyperfTest 默认已启用协程调度)
while ($result === null) {
Co::usleep(1000);
}
$this->assertEquals('done', $result);
}
}
hyperf/testing 的协程兼容性注意事项
不是所有 Swoole 协程 API 都能在测试中无差别使用,尤其要注意以下几点:
-
Co\Http\Client在 CLI 环境下需显式设置timeout,否则可能无限等待(测试进程无事件循环兜底) -
Co\Channel和Co\Socket类操作,在非 Swoole Worker 进程中行为受限,建议只用于轻量通信验证 - 数据库连接池(
Db)和 Redis(Redis)组件默认启用协程驱动,但需确认config/autoload/databases.php中'driver' => 'mysql'对应的是Hyperf\Database\Driver\PDO\MySqlDriver而非同步驱动 - 若测试中调用了
ApplicationContext::getContainer()->get(SomeService::class),该服务必须支持协程(比如不能含file_get_contents同步阻塞调用)
协程测试真正的难点不在写法,而在「隔离」——每个测试方法都运行在独立的协程上下文中,但全局状态(如静态属性、单例容器实例)仍会跨测试污染。别忘了在 setUp() 里重置关键状态,尤其是你手动注册进容器的 mock 对象或临时配置。


















