Symfony ObjectProphecy数组与对象表现差异:setUp初始化数组无法修改
问题与解答
测试代码
<?php use Monolog\Test\TestCase; use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophet; class MyBar { public int $bar = 0; } class MyFoo { public function fooArray(): array { return []; } public function fooBar(): MyBar { return new MyBar(); } public function fooInt(): int { return 0; } } final class SimpleTest extends TestCase { private Prophet $prophet; private ObjectProphecy $mock; private array $myArray; private MyBar $myBar; private int $myInt; public function getProphet(): Prophet { return $this->prophet; } protected function setUp(): void { $this->prophet = new Prophet(); $this->myArray = [0]; $this->myBar = new MyBar(); $this->myInt = 0; $this->mock = $this->getProphet()->prophesize(MyFoo::class); $this->mock->fooArray()->willReturn($this->myArray); $this->mock->fooBar()->willReturn($this->myBar); $this->mock->fooInt()->willReturn($this->myInt); } public function test(): void { $this->myArray = [1]; $this->assertEquals([0], $this->mock->reveal()->fooArray()); $this->myBar->bar = 1; $this->assertEquals(1, $this->mock->reveal()->fooBar()->bar); $this->myInt = 1; $this->assertEquals(0, $this->mock->reveal()->fooInt()); } }
问题
- 为何数组
$myArray、整型$myInt修改后不生效,但对象$myBar修改后生效? - 如何让数组像对象一样可以修改?(要求使用Prophecy框架内方案,不使用MockObject)
解答
1. 三者表现不一致的原因
这是PHP中值类型和引用类型的差异导致的:
- 整型
$myInt是值类型,setUp中调用willReturn($this->myInt)时,传递的是变量的值拷贝。后续修改$this->myInt,不会影响Prophecy内部保存的拷贝值。 - 数组在PHP中默认按值传递,
willReturn($this->myArray)传递的是数组的拷贝。后续重新赋值$this->myArray = [1],只是改变了当前变量指向的数组,和Prophecy保存的数组拷贝无关。 - 对象
$myBar是引用类型,willReturn($this->myBar)传递的是对象引用。后续修改对象属性,本质是操作同一个实例,所以Mock返回的对象属性会同步变化。
2. 让数组可修改的Prophecy方案
方式一:用闭包动态返回当前数组
在setUp中,将willReturn替换为will方法,传入闭包。每次调用Mock方法时,闭包会返回当前最新的$this->myArray:
$this->mock->fooArray()->will(function () { return $this->myArray; });
测试方法中修改$this->myArray后,Mock返回的就是更新后的数组。
方式二:用对象包裹数组
把数组封装到对象中,利用对象的引用特性间接实现数组可修改:
- 定义数组包裹类:
class ArrayWrapper { public array $data = []; }
- 修改测试类的变量和Mock设置:
private ArrayWrapper $myArrayWrapper; protected function setUp(): void { // 其他代码不变 $this->myArrayWrapper = new ArrayWrapper(); $this->myArrayWrapper->data = [0]; $this->mock->fooArray()->willReturn($this->myArrayWrapper->data); }
- 测试方法中修改对象内的数组:
$this->myArrayWrapper->data = [1]; $this->assertEquals([1], $this->mock->reveal()->fooArray());
通过对象引用传递的特性,修改对象内的数组时,Mock返回的内容会同步更新。
内容的提问来源于stack exchange,提问作者user_51
相关产品推荐
相关产品推荐

