By Benjamin Eberlei, first published at Tue, 08 Mar 2016 10:30:00 +0100
Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.
You can also buy a printed version of the book on Amazon or on epubli.
Today I want to share a simple trick for the excellent Mocking library Phake (I wrote about it before) when testing state on APIs that don't return values.
Testing becomes harder when you are using command / query separation in your code and service operations don't return values anymore. If your command method creates objects and passes them down the stack, then you usually want to make assertions on the nature of the changes.
Take the following example that creates a new Order
object:
<?php
class CheckoutHandler
{
private $orderRepository;
private $productRepository;
public function __construct($orderRepository, $productRepository)
{
$this->orderRepository = $orderRepository;
$this->productRepository = $productRepository;
}
public function checkout(Checkout $command)
{
$order = new Order();
$order->setAddress($command->address);
foreach ($command->productIds as $id => $amount) {
$product = $this->productRepository->find($id);
$order->addItem($product, $amount);
}
$this->orderRepository->save($order);
}
}
A "usual" PHPUnit test for this class can only make a single assertion that the OrderRepository
is called with an Order
object. But we might want know if a product was correctly assigned.
We can help you to set up continuous and automated testing for your projects. Book a workshop now.
With Phake::capture($value)
we can assign the argument passed to OrderRepository#save($order)
to a variable that is available inside the Unit-Test, ready to run assertions on.
<?php
class CheckoutHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testCheckout()
{
$orderRepository = \Phake::mock(OrderRepository::class);
$productRepository = \Phake::mock(ProductRepository::class);
$product = new Product();
\Phake::when($productRepository)->find(42)->thenReturn($product);
$handler = new CheckoutHandler($orderRepository, $productRepository);
$handler->checkout(new Checkout([
'productIds' => [42 => 1],
'address' => new Address(),
]));
\Phake::verify($orderRepository)->save(\Phake::capture($order));
$this->assertEquals(1, count($order->getProducts()));
$this->assertSame($product, $order->getProducts()[0]);
}
}
See after the \Phake::capture($order)
call, the $order
variable contains the argument that was passed to the OrderRepository from your code.
This argueably reaches into the tested class quite a bit, but when you use Command / Query separation and London-Style TDD the only way to observe behaviour and state is mocking. I still think Phake is the best mocking library for PHP and the capture method is another good argument for it.
Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.
Bart on Tue, 12 Apr 2016 21:11:41 +0200
Very nice article. thx
Link to comment