FunctionMock is a simple and elegant way to mock away system/global functions in your tests.

Instalation

composer require --dev avris/function-mock

Usage

Let’s say you have a class that writes something to a file:

<?php
namespace App\Service;

class WorkService
{
    public function work(int $input): bool
    {
        $result = 'correctResult'; // ... calculate the result somehow

        $filename = '/tmp/foo';

        return file_put_contents($filename, $result);
    }
}

To test it, without actually using filesystem, you can use FunctionMock:

<?php
namespace App\Test;

use App\Service\WorkService
use PHPUnit\Framework\TestCase;

class ServiceTest extends TestCase
{
    /** @var WorkService */
    private $service;

    protected function setUp()
    {
        $this->service = new WorkService();
    }

    protected function tearDown()
    {
        FunctionMock::clean();
    }

    public function testWork()
    {
        $mock = FunctionMock::create('App', 'file_put_contents', true);

        $returnValue = $this->service->work(8);

        $this->assertTrue($returnValue);
        $this->assertEquals([['/tmp/foo', 'correctResult']], $mock->getInvocations());
    }
}

If your tests are in the same namespace with your tested classes, it’s recommended to use __NAMESPACE__.

The third parameter, the return value of the mocked function, can either be a literal or a callable:

$mock = FunctionMock::create(__NAMESPACE__, 'file_put_contents', function ($filename, $data) {
    return $filename === '/tmp/foo';
});

You can disable/re-enable the mock by invoking $mock->disable() and $mock->enable(), as well as clear the list of logged invocations with $mock->clearInvocations().

Known limitations

FunctionMock works thanks to the fact that file_get_contents inside of App\Service namespace references to the function App\Service\file_get_contents and only if it’s not defined it falls back to the global \file_get_contents.

FunctionMock defines this App\Service\file_get_contents function on the fly and makes it adjustable to your needs in the runtime.

  • If you already have invoked file_get_contents inside of App\Service before registering a mock, PHP will continue using the global function and it cannot be overwritten anymore.
  • If you’re using \file_get_contents (explicitly global namespace) inside your tested class, there’s obviously no way to mock it.
  • After being registered, mock function cannot be really unset, only disabled.