Intro
Bien, veo que hay una solución para Mockery, así que como no me gusta Mockery, voy a darte una alternativa de Profecía, pero te sugiero que primero leas sobre la diferencia entre Mockery y Prophecy.
En pocas palabras : "La profecía utiliza un enfoque llamado enlace de mensajes ; significa que el comportamiento del método no cambia con el tiempo, sino que cambia con el otro método".
Código problemático del mundo real para cubrir
class Processor
{
/**
* @var MutatorResolver
*/
private $mutatorResolver;
/**
* @var ChunksStorage
*/
private $chunksStorage;
/**
* @param MutatorResolver $mutatorResolver
* @param ChunksStorage $chunksStorage
*/
public function __construct(MutatorResolver $mutatorResolver, ChunksStorage $chunksStorage)
{
$this->mutatorResolver = $mutatorResolver;
$this->chunksStorage = $chunksStorage;
}
/**
* @param Chunk $chunk
*
* @return bool
*/
public function process(Chunk $chunk): bool
{
$mutator = $this->mutatorResolver->resolve($chunk);
try {
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
$mutator->mutate($chunk);
$chunk->processingAccepted();
$this->chunksStorage->updateChunk($chunk);
}
catch (UnableToMutateChunkException $exception) {
$chunk->processingRejected();
$this->chunksStorage->updateChunk($chunk);
// Log the exception, maybe together with Chunk insert them into PostProcessing Queue
}
return false;
}
}
Solución PhpUnit Prophecy
class ProcessorTest extends ChunkTestCase
{
/**
* @var Processor
*/
private $processor;
/**
* @var MutatorResolver|ObjectProphecy
*/
private $mutatorResolverProphecy;
/**
* @var ChunksStorage|ObjectProphecy
*/
private $chunkStorage;
public function setUp()
{
$this->mutatorResolverProphecy = $this->prophesize(MutatorResolver::class);
$this->chunkStorage = $this->prophesize(ChunksStorage::class);
$this->processor = new Processor(
$this->mutatorResolverProphecy->reveal(),
$this->chunkStorage->reveal()
);
}
public function testProcessShouldPersistChunkInCorrectStatusBeforeAndAfterTheMutateOperation()
{
$self = $this;
// Chunk is always passed with ACK_BY_QUEUE status to process()
$chunk = $this->createChunk();
$chunk->ackByQueue();
$campaignMutatorMock = $self->prophesize(CampaignMutator::class);
$campaignMutatorMock
->mutate($chunk)
->shouldBeCalled();
$this->mutatorResolverProphecy
->resolve($chunk)
->shouldBeCalled()
->willReturn($campaignMutatorMock->reveal());
$this->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_IN_PROGRESS);
$self->chunkStorage
->updateChunk($chunk)
->shouldBeCalled()
->will(
function($args) use ($self) {
$chunk = $args[0];
$self->assertTrue($chunk->status() === Chunk::STATUS_PROCESSING_UPLOAD_ACCEPTED);
return true;
}
);
return true;
}
);
$this->processor->process($chunk);
}
}
Resumen
Una vez más, ¡la profecía es más asombrosa! Mi truco consiste en aprovechar la naturaleza vinculante de mensajería de Prophecy y, aunque lamentablemente parece un código infernal de javascript de devolución de llamada típico, comenzando con $ self = $ this; Como rara vez tienes que escribir pruebas unitarias como esta, creo que es una buena solución y definitivamente es fácil de seguir, depurar, ya que en realidad describe la ejecución del programa.
Por cierto: hay una segunda alternativa, pero requiere cambiar el código que estamos probando. Podríamos envolver a los alborotadores y trasladarlos a una clase separada:
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
podría envolverse como:
$processorChunkStorage->persistChunkToInProgress($chunk);
y eso es todo, pero como no quería crear otra clase para él, prefiero la primera.