A way to mock PHP internal functions with xepozz/internal-mocker
Tags: php, testing, mock, opensource
Published: 2024-03-10 04:57:59

**Goal**

Mock functions that have already “loaded” into PHP even before loading Composer Autoloader, any include or other `function name(){}` declarations

Mocking not only under a non-empty namespace, for example `App\Service\name` , but also from the root namespace: the easiest way to do this is through `use function name;`

**Problem**

If we declare a function with a name that already exists in the PHP standard library, we will get an error that such a function already exists and cannot be **overridden**.

It would be possible to “unload” it from memory, but functions cannot be unloaded from the memory in PHP.

You can only redefine a function before it is directly declared. But this method is not suitable, because the function is already declared before any call `php` .

**Research**

In php.ini you can find the `disable_functions` flag, which accepts a list of function names that should not be declared in PHP at startup.

If you use this flag, then `php -ddisable_functions=time -r "echo time();"` will throw an error:

```
❯ php -ddisable_functions=time -r "echo time();"
PHP Fatal error: Uncaught Error: Call to undefined function time() in Command line code:1
Stack trace:
#0 {main}
   thrown in Command line code on line 1

Fatal error: Uncaught Error: Call to undefined function time() in Command line code on line 1

Error: Call to undefined function time() in Command line code on line 1

Call Stack:
     0.0000 389568 1. {main}() Command line code:0

```

So logical. The `time` function no longer exists. But now you can create it yourself?

If you declare the function by yourself, there will no errors:

```
❯ php -ddisable_functions=time -r "function time() { return 123; } echo time();"
123%

```

**Bingo!**

We place the function declaration in the library, create a State manager, through which we can manage the return value “123” and create an interface for the user to interact with this manager.

Now, if a user wants to test the `time` call, we can independently specify the required values. Time in the future, in the past, 0, `false`, whatever.

But what if you need to test the modified `time` function only one time, and leave everything as is in other places?

You can do this: State manager creates a function for all tests that emulates the standard `time` , and in the desired test, overlay a private one on top of the general emulation.

Everything seems logical and understandable. You can code and enjoy testing.

However, how to emulate system time? If everything is clear with various polyfills from symfony: you can create some kind of function that will be based on another function, convert the result to a new format and return it.

But on what function should time be based?

`DateTime`* classes? `date()` ? `mktime` ? `hrtime` ? What if you need to turn them off too?

**Bash! 🤪**

PHP has an ability to refer to its big brother Bash at any time with simple backticks: ``command`` . The result will be a string, but you can always cast it.

For the analogue of `time()` the command is `date +%s` .

This means that the only thing left for the State manager to write is the ability to use not a static value, but a function that will be executed every time.

All this and more is done in the library **xepozz/internal-mocker**

We read the installation and initial setup document, add the necessary files, enter the following configuration:

```php
$mocker = new Mocker();
$mocker->load([
     [
         'namespace' => '',
         'name' => 'time',
         'function' => fn() => `date +%s`,
     ],
]);
MockerState::saveState();
```

And we get a generated mock for the `time` function, which will simply always work like a regular `time` in PHP itself

```php
namespace {
     use Xepozz\InternalMocker\MockerState;

     function time(...$arguments)
     {
         if (MockerState::checkCondition(__NAMESPACE__, "time", $arguments)) {
             return MockerState::getResult(__NAMESPACE__, "time", $arguments);
         }
         return MockerState::getDefaultResult(__NAMESPACE__, "time", fn () => `date +%s`);
     }
}

```

And it’s very easy to test:

```php
namespace Xepozz\InternalMocker\Tests\Integration;

use PHPUnit\Framework\TestCase;
use Xepozz\InternalMocker\MockerState;

use function time;

final class TimeTest extends TestCase
{
     public function testRun()
     {
         $this->assertEquals(`date +%s`, time());
     }

     public function testRun2()
     {
         MockerState::addCondition(
             '',
             'time',
             [],
             100
         );

         $this->assertEquals(100, time());
     }

     public function testRun3()
     {
         $this->assertEquals(`date +%s`, time());
     }

     public function testRun4()
     {
         $now = time();
         sleep(1);
         $next = time();

         $this->assertEquals(1, $next - $now);
     }
}
```

If someone wrote their own crutches or manually removed `use function` from files in order to replace functions in the desired namespace, now you can get rid of them and replace it with connecting

---

### Useful links

Documentation about `disable-functions`: https://www.php.net/manual/en/ini.core.php#ini.disable-functions

Internal mocker: https://github.com/xepozz/internal-mocker/

And this post was written weeks ago in my Telegram channel: https://t.me/handle_topic 😉

This is translation of the original post in https://habr.com/ru/articles/797343/