如何模拟一个使用 PHPUnit 实现迭代器接口的类?

2022-08-30 17:46:19

如何模拟以健壮方式实现迭代器接口的类的依赖项?


答案 1

网上已经有几个现有的解决方案可以解决这个问题,但是我见过的所有解决方案都有一个类似的弱点:它们依赖于.PHPUnit 中的 “expects at” 函数具有略微奇怪的行为,因为计数器用于对 mock 的每个方法调用。这意味着,如果您在直接的 foreach 之外对迭代器进行方法调用,则必须调整迭代器模拟。->expects($this->at(n))

解决此问题的方法是创建一个保存基本迭代器数据(源数组和位置)的对象,并将其传递到闭包中。因为它是通过引用传递的,所以对象在调用之间保持最新,因此我们可以模拟每个方法来模拟一个简单的迭代器。现在我们可以像往常一样使用迭代器模拟,而不必担心严格的调用顺序。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;
}

答案 2

如果你只需要针对泛型迭代器进行测试,那么PHP(在SPL扩展中 - 在PHP> 5.3中无法关闭)已经内置了实现迭代器的数组包装器:SPL迭代器。例如:

$mock_iterator = new \ArrayIterator($items);
$test_class->methodExpectingGenericIterator($mock_iterator);
$resulting_data = $mock_iterator->getArrayCopy();

推荐