Paulund

Demystifying Class Mocking in PHP: A Comprehensive Guide for Effective Testing

Testing is an important area of any application, when you write some code you need to make sure that all your logic is working correctly.

In this tutorial we're going to learn when and how you will mock a classes in your php tests.

When To Mock

Imagine we have a class to get information from a thrid party api, such as getting tweets for a specific user like below.

<?php

namespace Twitter;

use GuzzleHttp\Client;

class TwitterClient
{
    /**
     * @var Client
     */
    private $client;

    /**
     * TwitterClient constructor.
     * @param Client $client
     */
    public function __construct(Client $client)
    {
        $this->client = $client;
    }

    /**
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function getTweets()
    {
        return $this->client->get('https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=paulund');
    }
}

As you can see in this class it's dependency is the Guzzle client, we use this to send a GET request to the twitter API. The information we get back from this API can change over time, therefore if we have a test that depends on the same information then the test could fail over time.

Now if we have a class to get the latest tweets.

<?php

namespace Twitter;

class LatestTweets
{
    /**
     * @var TwitterClient
     */
    private $twitterClient;

    /**
     * LatestTweets constructor.
     * @param TwitterClient $twitterClient
     */
    public function __construct(TwitterClient $twitterClient)
    {
        $this->twitterClient = $twitterClient;
    }

    public function tweets()
    {
        return collect($this->twitterClient->getTweets())->take(5);
    }
}

As we're making sure we only get 5 tweets back from the API we need to test what happens if we return more than 5 tweets and less than 5 tweets.

By using mocking we can make sure that we define in our test exactly what comes back from the API to test specific responses.

How To Mock

To mock a class in PHP you can use the library Mockery.

The easiest way is to use mockery by using the static method and pass in the class you want to mock.

$twitterMock = \Mockery::mock(TwitterClient::class);

We can now use this mock object to change the response of the methods.

In our class LatestTweets we use the method getTweets to get the tweets for the profile. With mockery we can define what we get back from each method by using shouldReceive and andReturn.

$twitterMock->shouldReceive('getTweets')->andReturn([
[
    'text' => $faker->paragraph
],
[
    'text' => $faker->paragraph
],
[
    'text' => $faker->paragraph
]
]);

This makes sure we can guarantee what we get back from the API.

<?php

namespace Tests;

use Faker\Generator;
use Twitter\LatestTweets;
use Twitter\TwitterClient;

class LatestTweetsTest extends TestCase
{
    /** @test */
    public function it_returns_tweets_from_twitter_client()
    {
        // Given
        $faker = app(Generator::class);

        $twitterMock = \Mockery::mock(TwitterClient::class);
        $twitterMock->shouldReceive('getTweets')->andReturn([
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ]
        ]);

        // When
        $latestTweets = new LatestTweets($twitterMock);

        // Then
        $this->assertCount(3, $latestTweets->tweets());
    }

    /** @test */
    public function it_only_returns_5_tweets_from_twitter_client()
    {
        // Given
        $faker = app(Generator::class);

        $twitterMock = \Mockery::mock(TwitterClient::class);
        $twitterMock->shouldReceive('getTweets')->andReturn([
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ],
            [
                'text' => $faker->paragraph
            ]
        ]);

        // When
        $latestTweets = new LatestTweets($twitterMock);

        // Then
        $this->assertCount(5, $latestTweets->tweets());
    }
}