如何在PHPUnit 10及以上版本中配置ClockMock?
如何在PHPUnit 10+中正确使用Symfony ClockMock?
ClockMock是用于创建时间敏感测试的工具,比如根据日期是过去还是未来执行不同逻辑。它属于symfony/phpunit-bridge组件,在PHPUnit 8中可正常使用,而symfony/phpunit-bridge 7.2版本已支持PHPUnit 10+。
项目配置
- symfony: 6.4
- php: 8.2
- phpunit/phpunit: 10.5.41
- symfony/phpunit-bridge: 7.2.0
遇到的问题
官方文档曾要求在phpunit.xml中配置SymfonyTestsListener监听器,但PHPUnit 10+已不再支持<listeners>配置项,移除监听器后,测试代码无法使用指定的模拟时间,始终调用系统当前时间。
原测试代码:
class CompanyNameHelperTest extends TestCase { public function testGetCompanyNameByIdPast(): void { ClockMock::register(CompanyNameHelper::class); ClockMock::withClockMock(true); ClockMock::withClockMock(strtotime('2018-11-05 01:00:00')); $myDate = (new DateTimeImmutable(date('Y-m-d H:m:s'))); $companyId = 1; $this->assertEquals( 'ACME KG, Hamburg', CompanyNameHelper::getCompanyNameById($companyId, $myDate) ); ClockMock::withClockMock(false); } }
正确使用方式
1. 替换监听器为PHPUnit扩展配置
在phpunit.xml中,将原<listeners>配置替换为<extensions>:
<extensions> <extension class="Symfony\Bridge\PhpUnit\SymfonyTestsExtension"/> </extensions>
这是PHPUnit 10+支持的扩展加载方式,替代了旧的监听器机制。
2. 修正测试代码的ClockMock调用
- 移除多余的
ClockMock::withClockMock(true);调用,直接使用setTime()设置模拟时间 - 无需通过
date()函数构造DateTime,直接实例化DateTimeImmutable即可(ClockMock会拦截时间相关的系统调用) - 使用
ClockMock::reset()替代withClockMock(false)来重置时间模拟
修正后的测试代码:
class CompanyNameHelperTest extends TestCase { public function testGetCompanyNameByIdPast(): void { // 注册需要拦截时间调用的目标类 ClockMock::register(CompanyNameHelper::class); // 设置模拟的时间戳 ClockMock::setTime(strtotime('2018-11-05 01:00:00')); // 直接实例化DateTime,会使用模拟的时间 $myDate = new DateTimeImmutable(); $companyId = 1; $this->assertEquals( 'ACME KG, Hamburg', CompanyNameHelper::getCompanyNameById($companyId, $myDate) ); // 重置时间模拟,恢复系统时间 ClockMock::reset(); } }
3. 确保被测试类的时间调用被拦截
被测试的CompanyNameHelper类中,所有时间相关的操作(如date()、time()、new DateTime()等)必须是直接调用的,不能通过未注册的类或命名空间间接调用,否则ClockMock无法拦截这些调用。
内容的提问来源于stack exchange,提问作者Calamity Jane
相关产品推荐
相关产品推荐

