Mocks aren’t StubsXhoi KallupiBlockedUnblockFollowFollowingMay 26Photo by Dietmar Becker on UnsplashI want to clarify a confusion which has been on my mind lately.
There is a lot of new information when you start testing for the first time and to understand how Mocks and Stubs work is really important for testing.
Brief historyI work as a full-time software developer and while I was writing some tests I noticed one of my co-workers had written this line of code:(Due to privacy I am going to show sample code)$this->orderRepoStub = Mockery::mock(IOrderRepository::class);NOTE: Mockery is a PHP mock object framework and you can create a stub in the same way.
He postfixed the property name Stub while he was creating a Mock.
That made me dig deeper and finally understand the difference between the two.
Martin P.
Fowler has written a very good essay (Mocks Aren’t Stubs) about this topic.
(Yes, I copied his title.
)Back to our story.
I found out he created a mock because of this line of code:$this->orderRepoStub->shouldReceive('getById') ->once() ->with(self::DUMMY_ORDER_ID) ->andReturn($orderMock);Confused ?Test LifecyclePhoto by Angelina Litvin on UnsplashThere is a typical four phase sequence for xUnit tests to follow:Setup, Exercise, Verify, TeardownSequence with mocks:Setup, Setup EXPECTATIONS, Exercise, Verify EXPECTATIONS, Verify, TeardownMocks vs StubsFrom Mockery documentation:The difference between the two is that a stub only returns a preset result when called, while a mock needs to have expectations set on the method calls it expects to receive.
The key difference is that Mocks use behavior verification.
All the other test doubles(Stubs, Fake, Dummy, Spies) use state verification.
To understand this better let’s see an example.
I am going to use Mockery for this example but it shares the same logic as in other object-oriented languages.
Imagine we have an OrderService class where we update the order and an OrderRepository class which persist the Order in the database.
OrderService:// OrderServiceprivate $orderRepository;public function __construct(IOrderRepository $orderRepository){ $this->orderRepository = $orderRepository;}public function update(array $attributes): void{ $orderId = $attributes['id']; $order = $this->orderRepository->getById($orderId); $order->setName($attributes['name']); $order->setTotal($attributes['total']); $this->orderRepository->update($order);}Testing of the method update using Mock:// Setup$orderRepoMock = Mockery::mock(IOrderRepository::class);$orderService = new OrderService($orderRepoMock);$order = new Order(self::DUMMY_ORDER_ID, self::DUMMY_ORDER_NAME, self::DUMMY_ORDER_TOTAL);// Setup expectations$orderRepoMock->shouldReceive('getById') ->with(self::DUMMY_ORDER_ID) ->once() ->andReturn($order);$orderRepoMock->shouldReceive('update') ->once() ->with($order);// Exercise$expectedOrderAttributes = [ 'id' => self::DUMMY_ORDER_ID, 'name' => self::DUMMY_UPDATED_ORDER_NAME, 'total' => self::DUMMY_UPDATED_ORDER_TOTAL];$orderService->update($expectedOrderAttributes);// Verify Expectations// In this case PHPUnit will verify if the method getById and update // were called once.
If not test will FAIL!// Verify$this->assertEquals($expectedOrderAttributes['name'], $order->getName());$this->assertEquals($expectedOrderAttributes['total'], $order->getTotal());Using Mocks we need to set up expectations for our method calls.
Here we Mock the OrderService and we expect that getById and update method should be called only once.
Testing of the method update using Stub:// Setup$order = new Order(self::DUMMY_ORDER_ID, self::DUMMY_ORDER_NAME, self::DUMMY_ORDER_TOTAL);$orderRepoStub = Mockery::mock(IOrderRepository::class);$orderRepoStub->shouldReceive('getById') ->with(self::DUMMY_ORDER_ID) ->andReturn($order);$orderRepoStub->shouldReceive('update') ->with($order);$orderService = new OrderService($orderRepoStub);// Exercise$expectedOrderAttributes = [ 'id' => self::DUMMY_ORDER_ID, 'name' => self::DUMMY_UPDATED_ORDER_NAME, 'total' => self::DUMMY_UPDATED_ORDER_TOTAL];$orderService->update($expectedOrderAttributes);// Verify$this->assertEquals($expectedOrderAttributes['name'], $order->getName());$this->assertEquals($expectedOrderAttributes['total'], $order->getTotal());Using a Stub we need to set up his methods with configured return values.
Stubs don’t fail your tests, mocks can.
// OrderServicepublic function update(array $attributes): void{ $orderId = $attributes['id']; $order = $this->orderRepository->getById($orderId); $order->setName($attributes['name']); $order->setTotal($attributes['total']); // $this->orderRepository->update($order);}If we comment out the update call from OrderService the second test using Stub will NOT FAIL!Because Stubs don’t care if the method is called or not.
Last thought:Whether to use a Stub or a Mock it really depends on what you are testing.
Keep in mind that Mocks use behavior verification and the other test doubles use state verification.
.