web-dev-qa-db-ja.com

PHPUnitモックのreturnCallback()でオブジェクトを変更する

クラスのメソッドをモックして、パラメーターとして指定されたオブジェクトを変更するコールバックを実行したいと思います(PHP 5.3とPHPUnit3.5.5を使用)。

次のクラスがあるとしましょう。

class A
{
  function foobar($object) 
  {
    doSomething();
  }
}

そしてこのセットアップコード:

$mock = $this->getMockBuilder('A')->getMock();
$mock->expects($this->any())->method('foobar')->will(
  $this->returnCallback(function($object) {
    $object->property = something;
  }));

何らかの理由で、オブジェクトは変更されません。オン var_dumping $objectそれが正しいオブジェクトだと思います。オブジェクトが値によって渡される可能性がありますか?参照を受け取るようにモックを構成するにはどうすればよいですか?

20
Alex Lawrence

彼はアレックス、

私はその問題についてSebastian(phpunitの作成者)に話しましたが、そうです。引数はコールバックに渡される前にcloneedになります。

私は頭のてっぺんから回避策を提供することはできませんが、少なくともあなたが何も悪いことをしておらず、これは予想される動作であるとあなたに伝えるためにとにかく答えることを選びます。

セバスチャンのコメントを引用するには、IRCなぜそれが引数を複製するのかについて:

これが正しいかどうかは、私、私、そしてPHPUnitのユーザーの間で長年の議論です;-)

コピー/貼り付け可能なものを作成するには:

このコードサンプルのアサーション3は失敗します。 (変数は返されたオブジェクトでのみ変更されます)

<?php
class A
{
    function foobar($o)
    {
        $o->x = mt_Rand(5, 100);
    }
}

class Test extends PHPUnit_Framework_TestCase
{
    public function testFoo()
    {
        $mock = $this->getMock('A');
        $mock->expects($this->any())
             ->method('foobar')
             ->will($this->returnCallback(function($o) { $o->x = 2; return $o; }));

        $o = new StdClass;
        $o->x = 1;

        $this->assertEquals(1, $o->x);
        $return = $mock->foobar($o);

        $this->assertEquals(2, $return->x);
        $this->assertEquals(2, $o->x);
    }
}

更新:

PHPUnit 3.7以降、クローン作成をオフにすることができます。最後の議論を見てください:

public function getMock(
    $originalClassName, 
    $methods = array(), 
    array $arguments = array(), 
    $mockClassName = '', 
    $callOriginalConstructor = TRUE, 
    $callOriginalClone = TRUE, 
    $callAutoload = TRUE, 
    $cloneArguments = FALSE
);

デフォルトではオフになっている場合もあります:)

42
edorian

モックされたメソッドに渡されたパラメーターのクローン作成を実行するクラスは_PHPUnit_Framework_MockObject_Invocation_Static_です。 cloneObject()を見ると、パラメータのクラスの__clone()メソッドがパブリックでない場合、元のオブジェクトが返されることがわかります。

パラメータオブジェクトのクラスを制御できる場合および自分でクローンを作成する必要がない場合は、プライベートの空の__clone()メソッドを追加できます。

5
David Harkness

これは本当に古いですが、検索の先頭に表示され、正しい方向を示したので、更新する価値があります。 PHPUnit 6.0以降、次のようにdisableArgumentCloning()を使用します。

return $this->getMockBuilder('A')
    ->disableOriginalConstructor()
    ->disableArgumentCloning()
    ->setMethods(array('foobar'))
    ->getMock()
;
0
iisisrael