Intro
Okay, ich sehe, dass es eine Lösung für Mockery gibt. Da ich Mockery nicht mag, werde ich Ihnen eine Prophezeiungsalternative geben, aber ich würde Ihnen empfehlen, zuerst über den Unterschied zwischen Spott und Prophezeiung zu lesen.
Lange Rede, kurzer Sinn: "Prophecy verwendet einen Ansatz, der als Nachrichtenbindung bezeichnet wird. Dies bedeutet, dass sich das Verhalten der Methode nicht im Laufe der Zeit ändert, sondern durch die andere Methode."
Problematischer Code der realen Welt
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;
}
}
PhpUnit Prophecy Lösung
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);
}
}
Zusammenfassung
Wieder einmal ist Prophezeiung fantastischer! Mein Trick besteht darin, die Nachrichtenbindung von Prophecy zu nutzen, und obwohl es leider wie ein typischer Rückruf-Javascript-Höllencode aussieht, beginnend mit $ self = $ this; Da Sie sehr selten Unit-Tests wie diesen schreiben müssen, denke ich, dass dies eine gute Lösung ist und es definitiv einfach ist, zu folgen, zu debuggen, da es tatsächlich die Programmausführung beschreibt.
Übrigens: Es gibt eine zweite Alternative, die jedoch eine Änderung des zu testenden Codes erfordert. Wir könnten die Unruhestifter einwickeln und sie in eine separate Klasse bringen:
$chunk->processingInProgress();
$this->chunksStorage->updateChunk($chunk);
könnte verpackt werden als:
$processorChunkStorage->persistChunkToInProgress($chunk);
und das war's, aber da ich keine weitere Klasse dafür erstellen wollte, bevorzuge ich die erste.