DEV Community

Nikola Stojiljkovic
Nikola Stojiljkovic

Posted on • Updated on

Mock nested PHP default functions in PHPUnit tests

Let's say you have the following:

class ComposerFileService {
    public function fetchComposerJsonObject(string $composerJsonFilePath): ?object
    {
        return json_decode(file_get_contents($composerJsonFilePath));
    }
}
Enter fullscreen mode Exit fullscreen mode

To test this method in PHPUnit, use php-mock/php-mock-phpunit library. First, install it with:

composer require --dev php-mock/php-mock-phpunit
Enter fullscreen mode Exit fullscreen mode

The second step is to use \phpmock\phpunit\PHPMock trait in your PHPUnit test class. We can then write a unit test:

/**
 * @dataProvider fetchComposerJsonObject_HappyFlow
 *
 * @param string $composerJsonFilePath
 * @param string $fileGetContentsMockValue
 * @param object $jsonDecodeMockValue
 */
public function testFetchComposerJsonObject_HappyFlow(
    string $composerJsonFilePath,
    string $fileGetContentsMockValue,
    object $jsonDecodeMockValue
) {
    $fileGetContentsMock = $this->getFunctionMock(dirname(ComposerFileService::class), 'file_get_contents');
    $fileGetContentsMock->expects($this->once())
        ->with($composerJsonFilePath)
        ->willReturn($fileGetContentsMockValue);

    $jsonDecodeMock = $this->getFunctionMock(dirname(ComposerFileService::class), 'json_decode');
    $jsonDecodeMock->expects($this->once())
        ->with($fileGetContentsMockValue)
        ->willReturn($jsonDecodeMockValue);

    $service = $this->getMockBuilder(ComposerFileService::class)
        ->disableOriginalConstructor()
        ->onlyMethods([])
        ->getMock();
    $result = $service->fetchComposerJsonObject($composerJsonFilePath);

    $this->assertEquals($jsonDecodeMockValue, $result);
}
Enter fullscreen mode Exit fullscreen mode

Data provider for this test is simply:

private function fetchComposerJsonObject_HappyFlow(): array {
    $faker = Factory::create();

    return [
        'happy flow' => [
            'composerJsonFilePath' => $faker->unique()->word(),
            'fileGetContentsMockValue' => $faker->unique()->word(),
            'jsonDecodeMockValue' => (object)[ 'key' => $faker->unique()->word()]
        ]
    ];
}
Enter fullscreen mode Exit fullscreen mode

In order to properly test this method, we did the following:

  • $faker in the data provider is simply using https://fakerphp.github.io to generate random data for our tests. It's not that relevant for our example.
  • We are not using a real file from the filesystem. Unit tests should not do that.
  • file_get_contents and json_decode (PHP's default) functions are not called directly, but mocked.
  • We are testing that file_get_contents is getting called once, with the value of $composerJsonFilePath. The return value is randomly generated (by faker), and is irrelevant in the context of testing the file_get_contents function. We'll just need it in order to properly test json_decode.
  • We are testing that json_decode is getting called once, with the value of what file_get_contents returned.
  • And finally, we are testing that our method returned the result of calling json_decode.

For more information and examples of mocking and spying on PHP's default functions, take a look at the following libraries:

If you liked the article,...
Image description

Oldest comments (0)