PHP单元测试如何Mock被测类构造时调用的loadSettings方法注入无效配置
实现思路
针对“构造函数自动调用loadSettings、需要替换该方法给settings注入无效值以测试functionUnderTest行为”的需求,有三种成熟落地的方案,可根据测试颗粒度、原方法的依赖复杂度选择:
方案1:反射直接修改受保护属性(最直白零依赖)
loadSettings的核心作用是给protected $settings属性赋值,你完全不需要拦截方法本身的执行,在实例化完成后直接通过反射把settingToFake的值改成无效数据即可,全程不需要依赖测试框架的mock能力。
public function testFunctionToTestWithInvalidSetting() { // 正常走原类的构造流程实例化 $classUnderTest = new Foo('settingUsedInloadSettings'); // 反射解锁受保护属性的读写权限 $reflection = new \ReflectionClass($classUnderTest); $settingsProp = $reflection->getProperty('settings'); $settingsProp->setAccessible(true); // 直接修改目标配置值为无效数据 $currentSettings = $settingsProp->getValue($classUnderTest); $currentSettings['settingToFake'] = 'invalid_value_to_trigger_error_response'; $settingsProp->setValue($classUnderTest, $currentSettings); // 执行待测试方法做断言 $actual = $classUnderTest->functionUnderTest(); $expected = ['expectedErrorResponse']; $this->assertEquals($expected, $actual); }
- 优势:逻辑直白,不会出现mock配置不生效的问题
- 适用场景:原
loadSettings只是简单赋值、没有触发外部依赖的场景
方案2:测试专用子类重写方法(最快无框架依赖)
如果你需要避免原loadSettings里的数据库查询、文件读取等外部逻辑,完全可以在测试文件里定义一个Foo的轻量子类,直接重写loadSettings方法注入无效值,构造时自动调用重写后的实现即可。
// 测试文件内定义专用测试子类 class InvalidSettingStubFoo extends Foo { protected function loadSettings() { // 完全替换原方法逻辑,不跑外部依赖 $this->settings['settingToFake'] = 'invalid_value_to_trigger_error_response'; } } public function testFunctionToTestWithInvalidSetting() { // 实例化子类,构造阶段就会触发被重写的loadSettings $classUnderTest = new InvalidSettingStubFoo('settingUsedInloadSettings'); $actual = $classUnderTest->functionUnderTest(); $expected = ['expectedErrorResponse']; $this->assertEquals($expected, $actual); }
- 优势:可读性极高,不会触发原方法的外部依赖,测试速度快,完全不需要掌握PHPUnit的mock语法
- 适用场景:原
loadSettings包含外部依赖、需要隔离测试的场景
方案3:PHPUnit部分模拟实现(无需单独写子类)
如果不想单独定义子类,可通过PHPUnit的部分模拟能力,只替换loadSettings这一个方法的实现,其余方法(构造函数、functionUnderTest)全部保留原类的真实逻辑,效果和子类方案完全一致。
public function testFunctionToTestWithInvalidSetting() { // 构建测试替身 $classUnderTest = $this->getMockBuilder(Foo::class) // 仅替换loadSettings方法,其余方法全用原真实实现 ->onlyMethods(['loadSettings']) // 配置可访问受保护方法 ->setAccessibleMethods(['loadSettings']) // 保留原类的构造逻辑,传入原有参数 ->setConstructorArgs(['settingUsedInloadSettings']) ->getMock(); // 配置被替换的loadSettings的行为 $classUnderTest->expects($this->any()) ->method('loadSettings') ->willReturnCallback(function() { $this->settings['settingToFake'] = 'invalid_value_to_trigger_error_response'; }); $actual = $classUnderTest->functionUnderTest(); $expected = ['expectedErrorResponse']; $this->assertEquals($expected, $actual); }
注意:不要直接mock整个Foo类,否则会把构造函数、待测试方法也替换成空实现,完全失去测试意义。
内容的提问来源于stack exchange,提问作者Melchior
相关产品推荐
相关产品推荐

