Mock nested PHP default functions in PHPUnit tests
Tags: php, testing, phpunit, tutorial
Published: 2023-01-28 23:07:30

Let's say you have the following:

```php
class ComposerFileService {
    public function fetchComposerJsonObject(string $composerJsonFilePath): ?object
    {
        return json_decode(file_get_contents($composerJsonFilePath));
    }
}
```
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
```
The second step is to use `\phpmock\phpunit\PHPMock` trait in your PHPUnit test class. We can then write a unit test:

```php
/**
 * @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);
}
```

Data provider for this test is simply:
```php
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()]
        ]
    ];
}
```

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:

- https://github.com/php-mock/php-mock - a testing library which mocks non deterministic built-in PHP functions;
- https://github.com/php-mock/php-mock-phpunit - integrates the function mock library PHP-Mock with PHPUnit;

If you liked the article,...
[![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kugdh9szk858ncio2mzi.png)](https://www.buymeacoffee.com/puEW3HvWvP)