Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I mock a class that implements the Iterator interface using PHPUnit?

Tags:

How can I mock a dependency for my class that implements the Iterator interface in a robust manner?

like image 298
Dan Avatar asked Apr 09 '13 16:04

Dan


1 Answers

There's a couple of existing solutions to this problem online already but all of the ones I've seen share a similar weakness: they rely on ->expects($this->at(n)). The 'expects at' function in PHPUnit has slightly odd behaviour in that the counter is for every method call to the mock. This means that if you have method calls to your iterator outside of a straight forward foreach you have to adjust your iterator mock.

The solution to this is to create an object holding the basic iterator data (source array and position) and pass that into returnCallback closures. Because it's passed by reference the object is kept up to date between calls so we can mock each method to simulate a simple iterator. Now we can use the iterator mock as normal without having to worry about a rigid call order.

Sample method below that you can use to setup an iterator mock:

/**  * 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; } 
like image 58
Dan Avatar answered Oct 04 '22 09:10

Dan