PHPUnit回调意外修改外部变量:测试预期对象被改动原因及更优方案
PHP 工厂方法测试问题解析
疑问1:为何$this->object的修改会同步改动外部的$expect变量?
这是PHP对象类型的基础赋值机制决定的:
- PHP中对象类型的变量存储的不是对象本身,而是指向堆内存中对象实例的引用句柄,普通赋值不会触发对象拷贝。
- 代码中
$this->object = $expect;本质是让$this->object和$expect指向完全相同的一个对象实例,不存在独立的内存拷贝。 - 后续mock回调里执行
$this->object->token = $passed_value时,修改的是两个变量共享的内存对象,因此外部的$expect属性会被同步更新。 - 这也解释了为什么原本预期失败的测试反而通过:最后断言时,
$expect的token已经在回调执行阶段被改成了传入的Bar,和返回的$manager完全一致,自然不会触发断言失败。
疑问2:更优的createManager方法测试方案
首先明确该方法的核心职责:将收到的类名参数原样传递给依赖的ManagerFactory实例的createManager方法,并将工厂的返回值透传给调用方,不做额外加工。测试需要覆盖两个核心校验点:
- 调用工厂方法时,传递的类名参数是否和入参完全一致
- 工厂返回的对象是否被该方法原样返回,无中间修改
对应优化写法完全不需要依赖自定义returnCallback,直接用PHPUnit原生API即可,从根源上避免引用问题:
/** * @test */ public function createManagerReturnsObject() { // 初始化被测试类实例 $util = new Util(); // 构造独立的工厂mock对象 $mockFactory = $this->getMockBuilder(ManagerFactory::class) ->onlyMethods(['createManager']) ->getMock(); // 定义独立的预期返回对象,无跨变量引用 $expectedReturn = (object)['token' => 'Foo']; // 定义mock行为:验证调用参数正确,返回独立的预期对象 $mockFactory->expects($this->once()) ->method('createManager') ->with('Bar') // 直接校验传入工厂的类名是否正确 ->willReturn($expectedReturn); // 无自定义回调,直接返回独立对象 // 注入mock依赖 $util->managerFactory = $mockFactory; // 执行被测方法 $actualReturn = $util->createManager('Bar'); // 用严格相等校验,确认返回的就是mock返回的同一个实例 $this->assertSame($expectedReturn, $actualReturn); }
这种写法的优势:
- 从根源规避引用问题:全程没有跨变量共享对象内存,不存在
$expect被意外修改的可能 - 测试逻辑更严谨:通过
$this->once()和with('Bar')直接验证方法对依赖的调用行为,而不是仅校验最终返回值 - 用
assertSame替代assertEquals:精准匹配“原样透传对象”的方法设计,避免松散相等判断带来的测试盲区 - 不需要写自定义回调逻辑,减少冗余代码和潜在的引用副作用
内容的提问来源于stack exchange,提问作者Mindaugas
相关产品推荐
相关产品推荐

