MockClock 必须通过 Symfony 的 ClockInterface 依赖注入机制生效,直接 new 无法影响业务逻辑;常见错误是测试中创建了 MockClock 但服务仍用默认 SystemClock,需确保被测类接收 ClockInterface 且容器正确覆盖该服务。

MockClock 能精准控制时间流逝,但必须配合 Symfony 的 ClockInterface 注入机制才能生效——直接 new MockClock 并不能影响业务逻辑中的时间判断。
为什么优惠逻辑没响应 MockClock?
常见错误是只在测试里创建了 MockClock,但服务容器或被测类仍使用默认的 SystemClock。Symfony 的时间敏感组件(如 RateLimiter、自定义优惠过期检查)默认依赖注入的 ClockInterface 实例,若未显式替换,测试中改了 MockClock 的时间,业务代码仍调用系统时钟。
- 确认被测服务是否通过构造函数或 setter 接收
ClockInterface(不是硬编码new \DateTimeImmutable()) - 检查测试中是否将
MockClock实例正确传入服务(例如:用new DiscountService($mockClock),而非依赖容器自动解析) - 若用容器加载服务,需在测试容器中 override
ClockInterface服务定义,指向MockClock实例
如何用 MockClock 模拟“优惠从生效到过期”的全过程?
MockClock 不是“跳转时间”,而是“偏移基准时间”。它内部维护一个 DateTimeImmutable 基准值,sleep() 和 advance() 方法会移动该基准。因此要模拟时间流逝,得先设初始时间,再推进。
- 初始化时用
$mockClock = new MockClock(new \DateTimeImmutable('2024-01-01 10:00:00'))设定起始点 - 判断优惠是否生效:调用
$mockClock->now()获取当前模拟时间,与优惠startsAt字段比较 - 模拟 2 小时后:调用
$mockClock->advance(7200)(单位为秒),再调用$mockClock->now()即得2024-01-01 12:00:00 - 若优惠有效期 24 小时,推进 86400 秒后,
$mockClock->now()应等于expiresAt,再推进即过期
测试中容易忽略的 DateTimeZone 和精度问题
MockClock 默认使用 date_default_timezone_get() 的时区,若优惠逻辑依赖 UTC 或特定时区(如 Asia/Shanghai),而测试环境时区不一致,会导致 now() 返回的时间戳与预期不符。
- 显式指定时区:用
new \DateTimeImmutable('2024-01-01 10:00:00', new \DateTimeZone('UTC'))初始化MockClock - 避免使用
strtotime()或模糊字符串(如'+2 hours')初始化,它依赖本地时区且不保证毫秒级一致性 - 不要依赖
sleep()方法做精确等待——它只是语义化别名,实际仍是advance();真正需要“等待”行为应由测试流程控制,而非让 CPU 真睡
最关键的细节是:MockClock 只改变它自己返回的时间,不会重写 PHP 的全局时间函数(如 time() 或 date())。所有时间判断必须经过你注入的 ClockInterface 实例,否则模拟完全失效。


















