web-dev-qa-db-ja.com

PHPUnit「モックされたメソッドは存在しません。」 $ mock-> expected($ this-> at(...))を使用する場合

PHPUnitモックオブジェクトで奇妙な問題が発生しました。 2回呼び出す必要があるメソッドがあるので、「at」マッチャーを使用しています。これは、メソッドが初めて呼び出されたときに機能しますが、何らかの理由で、2回目に呼び出されたときに、「モックされたメソッドが存在しません」というメッセージが表示されます。私は以前に「at」マッチャーを使用したことがあり、これに遭遇したことはありません。

私のコードは次のようになります。

class MyTest extends PHPUnit_Framework_TestCase
{
    ...

    public function testThis()
    {
        $mock = $this->getMock('MyClass', array('exists', 'another_method', '...'));
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(1))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));
    }

    ...
}

テストを実行すると、次のようになります。

Expectation failed for method name is equal to <string:exists> when invoked at sequence index 1.
Mocked method does not exist.

2番目のマッチャーを削除しても、エラーは発生しません。

誰かがこれに遭遇したことがありますか?

ありがとう!

38
rr.

問題は、「at」マッチャーが機能することをどのように理解したかということになりました。また、私の例は、単体テストでどのようになっているのかを逐語的に示していませんでした。 「at」マッチャーカウンターはクエリごとに機能すると思いましたが、実際にはオブジェクトごとのインスタンスごとに機能します。

例:

class MyClass {

    public function exists($foo) {
        return false;
    }

    public function find($foo) {
        return $foo;
    }
}

間違った例:

class MyTest extends PHPUnit_Framework_TestCase
{

    public function testThis()
    {
        $mock = $this->getMock('MyClass');
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(0))
             ->method('find')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue('foo'));

        $mock->expects($this->at(1))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));

        $this->assertTrue($mock->exists("foo"));
        $this->assertEquals('foo', $mock->find('foo'));
        $this->assertFalse($mock->exists("bar"));
    }

}

正しい:

class MyTest extends PHPUnit_Framework_TestCase
{

    public function testThis()
    {
        $mock = $this->getMock('MyClass');
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(1))
             ->method('find')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue('foo'));

        $mock->expects($this->at(2))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));

        $this->assertTrue($mock->exists("foo"));
        $this->assertEquals('foo', $mock->find('foo'));
        $this->assertFalse($mock->exists("bar"));
    }

}
44
rr.

参考までに、関連するかどうかはわかりませんが、同じことが発生しましたが、$this->at()メソッドでは発生しませんでした。私にとっては、$this->never()メソッドでした。

これによりエラーが発生しました

_$mock->expects($this->never())
    ->method('exists')
    ->with('arg');
_

これでエラーが修正されました

_$mock->expects($this->never())
    ->method('exists');  
_

$this->exactly(0)メソッドを使用した場合も同じことをしました。

これが誰かを助けることを願っています。

17
yvoyer

$this->at(1)$this->at(2)に変更してみてください

5
Alan

これは、PHPUnitによるエラーメッセージの残念な表現です。

@ rrの回答の言及のように、呼び出しの順序を再確認してください。

私の場合、自分のコードで知る限り、それぞれat(0)at(1)を使用する必要がありますが、at(2)とを使用するまでは使用しませんでした。代わりにat(3)が機能しました。 (私はCakePHPでセッションモックを使用しています。)

順序を確認する最良の方法は、呼び出されたメソッドに「入り」、何が渡されたかを確認することです。あなたはこのようにそれを行うことができます:

$cakePost = $this->getMock('CakePost');
$cakePost->expects($this->once())
->method('post')
->with(
    // Add a line like this for each arg passed
    $this->callback(function($arg) {
        debug("Here's what was passed: $arg");
    })
);
4
Tyler Collier

デモコードからわかる限り、それはshould動作します。古いバージョンのPHPUnitを実行していて、それが機能するかどうかを確認したい場合に備えて、実用的な例を作成しました。

それが役に立たない場合は、もう少し(せいぜい実行可能)コードを提供できますか? :)

<?php

class MyTest extends PHPUnit_Framework_TestCase
{

    public function testThis()
    {
        $mock = $this->getMock('MyClass');
        $mock->expects($this->at(0))
             ->method('exists')
             ->with($this->equalTo('foo'))
             ->will($this->returnValue(true));

        $mock->expects($this->at(1))
             ->method('exists')
             ->with($this->equalTo('bar'))
             ->will($this->returnValue(false));

        $this->assertTrue($mock->exists("foo"));
        $this->assertFalse($mock->exists("bar"));
    }

}

class MyClass {

    public function exists($foo) {
        return false;
    }
}

印刷

phpunit MyTest.php
PHPUnit 3.4.15 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 4.25Mb

OK (1 test, 3 assertions)
1
edorian

MyClassをテストに含めてもよろしいですか?クラス/インターフェースを含めずにモックすると、未定義のメソッドエラーが発生しました。

0
martinvium

質問が提起されたときではないかもしれませんが、今日 ドキュメント atの使用方法を明確に指定しており、引用します

注意
at()マッチャーの$ indexパラメーターは、特定のモックオブジェクトのすべてのメソッド呼び出しで、ゼロから始まるインデックスを参照します。このマッチャーを使用する場合は、特定の実装の詳細に密接に関連している脆弱なテストにつながる可能性があるため、注意が必要です。

0
Mubashar