23

Iteratorインターフェイスを堅牢な方法で実装するクラスの依存関係をモックするにはどうすればよいですか?

4

3 に答える 3

24

この問題に対する既存の解​​決策がオンラインで既にいくつかありますが、私が見たものはすべて同じような弱点を共有してい->expects($this->at(n))ます。PHPUnit の「expects at」関数は、カウンターがモックへのすべてのメソッド呼び出しに対してあるという点で、少し奇妙な動作をします。これは、単純な foreach の外でイテレータへのメソッド呼び出しがある場合、イテレータ モックを調整する必要があることを意味します。

これに対する解決策は、基本的なイテレータ データ (ソース配列と位置) を保持するオブジェクトを作成し、それをreturnCallbackクロージャに渡すことです。参照によって渡されるため、オブジェクトは呼び出し間で最新の状態に保たれるため、各メソッドをモックして単純な反復子をシミュレートできます。これで、厳密な呼び出し順序を気にすることなく、イテレータ モックを通常どおり使用できます。

イテレータ モックをセットアップするために使用できる以下のサンプル メソッド:

/**
 * Setup methods required to mock an iterator
 *
 * @param PHPUnit_Framework_MockObject_MockObject $iteratorMock The mock to attach the iterator methods to
 * @param array $items The mock data we're going to use with the iterator
 * @return PHPUnit_Framework_MockObject_MockObject The iterator mock
 */
public function mockIterator(PHPUnit_Framework_MockObject_MockObject $iteratorMock, array $items)
{
    $iteratorData = new \stdClass();
    $iteratorData->array = $items;
    $iteratorData->position = 0;

    $iteratorMock->expects($this->any())
                 ->method('rewind')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             $iteratorData->position = 0;
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('current')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return $iteratorData->array[$iteratorData->position];
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('key')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return $iteratorData->position;
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('next')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             $iteratorData->position++;
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('valid')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return isset($iteratorData->array[$iteratorData->position]);
                         }
                     )
                 );

    $iteratorMock->expects($this->any())
                 ->method('count')
                 ->will(
                     $this->returnCallback(
                         function() use ($iteratorData) {
                             return sizeof($iteratorData->array);
                         }
                     )
                 );

    return $iteratorMock;
}
于 2013-04-09T16:19:41.737 に答える