Come posso prendere in giro una dipendenza per la mia classe che implementa l'interfaccia Iterator
in modo affidabile?Come posso prendere in giro una classe che implementa l'interfaccia Iterator usando PHPUnit?
risposta
Esistono già un paio di soluzioni esistenti per questo problema in linea ma tutte quelle che ho visto condividono un punto debole simile: si basano su ->expects($this->at(n))
. La funzione 'expect at' in PHPUnit ha un comportamento un po 'strano in quanto il contatore è per ogni chiamata di metodo alla simulazione. Ciò significa che se si hanno chiamate di metodo al proprio iteratore al di fuori di un foreach diretto, è necessario regolare l'iteratore simulato.
La soluzione è creare un oggetto contenente i dati di iterazione di base (matrice e posizione) e passarlo nelle chiusure returnCallback
. Poiché viene passato per riferimento, l'oggetto viene tenuto aggiornato tra le chiamate in modo da poter prendere in giro ciascun metodo per simulare un semplice iteratore. Ora possiamo usare l'iteratore come normale senza doversi preoccupare di un rigido ordine di chiamata.
metodo di esempio che è possibile utilizzare per impostare un iteratore finto:
/**
* 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;
}
Se avete solo bisogno di testare contro un iteratore generica, quindi PHP (nell'estensione SPL - che non può essere disattivato in PHP> 5.3) ha incorporato i wrapper di array che implementano Iterable: SPL Iterators. per esempio.
$mock_iterator = new \ArrayIterator($items);
$test_class->methodExpectingGenericIterator($mock_iterator);
$resulting_data = $mock_iterator->getArrayCopy();
Ecco una soluzione che combina il meglio dei due mondi, con un ArrayIterator
internamente:
/**
* @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;
}
Stai rispondendo alla tua domanda? – epicdev
Sto postando una soluzione per un problema che ho riscontrato. Dal momento che non ho un blog l'ho messo qui. Presumo che sia un modo valido per usare SO dal momento che c'è una casella per farlo quando si pubblica una domanda :) – Dan
Proprio come sei, @Dan. – Travesty3