Introduction
Well, I see that there is one solution for Mockery, since I do not like Mockery, I will give you an alternative to Prophecy, but first I suggest you read about the difference between Mockery and Prophecy first.
In short : "Prophecy takes a binding approach - this means that the behavior of the method does not change over time, but rather changes by another method. "
Real world problem code to cover
class Processor { private $mutatorResolver; private $chunksStorage; public function __construct(MutatorResolver $mutatorResolver, ChunksStorage $chunksStorage) { $this->mutatorResolver = $mutatorResolver; $this->chunksStorage = $chunksStorage; } 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);
PhpUnit Prophecy Solution
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); } }
Summary
Again, the Prophecy is more amazing! My trick is to use the connecting nature of the Prophecy, and although this unfortunately looks like a typical, add-on javascript callback, starting with $ self = $ this; , since you very rarely have to write unit tests. I think this is a pleasant solution, and it is definitely easy to track, debug, because it actually describes the execution of the program.
BTW: There is a second alternative, but it requires changing the code that we are testing. We could fool troublemakers and move them to a separate class:
$chunk->processingInProgress(); $this->chunksStorage->updateChunk($chunk);
can be wrapped as:
$processorChunkStorage->persistChunkToInProgress($chunk);
and that he, but since I did not want to create another class for him, I prefer the first.
EnchanterIO Aug 04 '17 at 15:21 2017-08-04 15:21
source share