Jak mogę sfałszować zależność dla mojej klasy, która implementuje interfejs Iterator
w solidny sposób?Jak mogę kpić z klasy, która implementuje interfejs Iteratora za pomocą PHPUnit?
Odpowiedz
Istnieje kilka istniejących rozwiązań tego problemu w Internecie, ale wszystkie te, które widziałem, mają podobną słabość: polegają na ->expects($this->at(n))
. Funkcja "oczekuje na" w PHPUnit ma nieco dziwne zachowanie, ponieważ licznik jest dla każdego wywołania metody do próbnego. Oznacza to, że jeśli masz wywołania metod do twojego iteratora poza prostym foreach, musisz dostosować swoje fałszywe iteratory.
Rozwiązaniem tego problemu jest utworzenie obiektu zawierającego podstawowe dane iteratora (tablica źródłowa i położenie) i przekazanie go do zamknięć returnCallback
. Ponieważ jest przekazywana przez odniesienie, obiekt jest aktualizowany między wywołaniami, dzięki czemu możemy kpić z każdej metody, aby symulować prosty iterator. Teraz możemy używać iteratora jako normalnego bez obawy o sztywne zamówienie.
metoda Próbka poniżej którego można użyć do konfiguracji iterator makiety:
/**
* 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;
}
Jeśli wystarczy przetestować przed rodzajowego iterator, wówczas PHP (w rozszerzeniu SPL - które nie mogą być wyłączone w PHP> 5.3) ma wbudowane owijarki tablicowe, które implementują Iterable: SPL Iterators. na przykład
$mock_iterator = new \ArrayIterator($items);
$test_class->methodExpectingGenericIterator($mock_iterator);
$resulting_data = $mock_iterator->getArrayCopy();
Oto rozwiązanie, które łączy w sobie najlepsze z obu światów, stosując ArrayIterator
wewnętrznie:
/**
* @param array $items
*
* @return \PHPUnit_Framework_MockObject_MockObject|SomeIterator
*/
private function createSomeIteratorMock(array $items = [])
{
$someIterator = $this->createMock(SomeIterator::class)->getMock();
$iterator = new \ArrayIterator($items);
$someIterator
->expects($this->any())
->method('rewind')
->willReturnCallback(function() use ($iterator) {
$iterator->rewind();
})
;
$someIterator
->expects($this->any())
->method('current')
->willReturnCallback(function() use ($iterator) {
return $iterator->current();
})
;
$someIterator
->expects($this->any())
->method('key')
->willReturnCallback(function() use ($iterator) {
return $iterator->key();
})
;
$someIterator
->expects($this->any())
->method('next')
->willReturnCallback(function() use ($iterator) {
$iterator->next();
})
;
$someIterator
->expects($this->any())
->method('valid')
->willReturnCallback(function() use ($iterator) {
return $iterator->valid();
})
;
return $someIterator;
}
Czy odpowiadając na własne pytanie? – epicdev
Publikuję rozwiązanie napotkanego problemu. Ponieważ nie mam bloga, umieszczam go tutaj. Zakładam, że jest to prawidłowy sposób używania SO, ponieważ jest pole wyboru, aby to zrobić po opublikowaniu pytania :) – Dan
Właśnie, jesteś @Dan. – Travesty3