PHPUnit9 被Mock方法仍执行原逻辑的解决方法
PHPUnit Mock类方法时原方法仍执行问题排查
问题复现
单元测试中Mock Twilio服务API调用的初始代码:
$message = $twilioTest->getMockedMessageInstance(['body' => 'This won\'t exist']); $twilioStub = $this->getMockBuilder(Twilio::class)->getMock(); $twilioStub->expects($this->once())->method('retrieveLastTextFromBot')->willReturn($message);
被Mock的retrieveLastTextFromBot方法原生实现逻辑:
$messages = $this->twilioClient->messages->read([ 'to' => TwilioDefinitions::getToNumber(), 'from' => getenv('TWILIO_NUMBER'), ], 1); if (count($messages) !== 1) { throw new NoExtantTextException('No previous message from ' . getenv('TWILIO_NUMBER') . ' to ' . TwilioDefinitions::getToNumber()); } return $messages[0];
预期Mock后方法内部逻辑不会执行,直接返回预设的$message,但实际运行测试触发403报错:
1) CronControllerTest::testRemindMethodErrorHandling Twilio\Exceptions\RestException: [HTTP 403] Unable to fetch page: Resource not accessible with Test Account Credentials /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/vendor/twilio/sdk/src/Twilio/Page.php:58 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/vendor/twilio/sdk/src/Twilio/Page.php:34 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/vendor/twilio/sdk/src/Twilio/Rest/Api/V2010/Account/MessagePage.php:23 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/vendor/twilio/sdk/src/Twilio/Rest/Api/V2010/Account/MessageList.php:147 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/vendor/twilio/sdk/src/Twilio/Rest/Api/V2010/Account/MessageList.php:96 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/vendor/twilio/sdk/src/Twilio/Rest/Api/V2010/Account/MessageList.php:118 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/app/YmcaScheduler/Utility/Twilio.php:33 /Users/adammcgurk/Desktop/ymca-scheduler-brains/src/app/YmcaScheduler/Controller/CronController.php:29 /Users/adammcgurk/Desktop/ymca-scheduler-brains/tests/CronControllerTest.php:22
调用栈定位到Twilio.php第33行,即retrieveLastTextFromBot内部调用Twilio API的代码,证明原方法逻辑确实被执行。
解决方案
按出现概率从高到低排查修复:
- 未显式指定要Mock的方法列表:旧版本PHPUnit调用
getMock()无参时不会自动替换所有类方法,必须通过setMethods()(PHPUnit 8及以下版本)或onlyMethods()(PHPUnit 9及以上版本)指定要重写的方法,修改Mock生成代码:
// PHPUnit 9+ 写法 $twilioStub = $this->getMockBuilder(Twilio::class) ->onlyMethods(['retrieveLastTextFromBot']) ->getMock(); $twilioStub->expects($this->once()) ->method('retrieveLastTextFromBot') ->willReturn($message);
注意:onlyMethods()仅接受类中真实存在的方法名,如果要Mock不存在的方法用addMethods()。
- Mock实例未注入被测对象:检查
CronController的初始化逻辑,确认生成的$twilioStub被正确注入到被测控制器实例中,排除被测类内部自行实例化Twilio对象、容器获取到真实Twilio实例的情况。 - 被Mock方法不可重写:如果
retrieveLastTextFromBot带有final、private、static修饰符,PHPUnit无法重写该方法,会直接执行原逻辑。此时需要调整代码结构,移除对应修饰符将方法改为可重写的public/protected非final方法,或直接Mock底层依赖的twilioClient实例。
内容的提问来源于stack exchange,提问作者Adam McGurk
相关产品推荐
相关产品推荐

