Logo

dev-resources.site

for different kinds of informations.

A way to mock PHP internal functions with xepozz/internal-mocker

Published at
3/10/2024
Categories
php
testing
mock
opensource
Author
xepozz
Categories
4 categories in total
php
open
testing
open
mock
open
opensource
open
Author
6 person written this
xepozz
open
A way to mock PHP internal functions with xepozz/internal-mocker

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

Enter fullscreen mode Exit fullscreen mode

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%

Enter fullscreen mode Exit fullscreen mode

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:

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

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

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`);
     }
}

Enter fullscreen mode Exit fullscreen mode

And it’s very easy to test:

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);
     }
}
Enter fullscreen mode Exit fullscreen mode

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/

mock Article's
30 articles in total
Favicon
FAQ — Bloomer Mock Data Generator
Favicon
Best Practices: How to Make Your API Smarter and More Flexible
Favicon
Testing ReactJS Context - A Guide with test-doubles
Favicon
The beauty of MSW
Favicon
Realistic data Generation Using Bloomer Mock
Favicon
Testing Spring Boot Applications: Unit, Integration, and Mocking — A Comprehensive Guide
Favicon
How to debug your Python mocks or imports
Favicon
Is there any option for Mock EncryptAsync() in Azure.Security.KeyVault.Keys.Cryptography
Favicon
Learn to Simulate Online APIs Without Server Infrastructure
Favicon
Diferenças entre o jest.spyOn e jest.mock
Favicon
Mock Class Constructor in Jest Test with Mocking Partials
Favicon
You stub/mock incorrectly
Favicon
Step-by-Step Tutorial on Setting Up a Mock Server Using Postman
Favicon
Mocking ES6 Class Instances with Jest
Favicon
Create free mock apis with unlimited storage
Favicon
Free API server with unlimited access
Favicon
Handle Variables and Dynamic Values with `jest.fn()`
Favicon
A way to mock PHP internal functions with xepozz/internal-mocker
Favicon
Mock vs Stub vs Fake: Understand the difference
Favicon
Mock modules properly with Jest and Typescript
Favicon
Simplified Strategies for Mocking API Calls
Favicon
Fastify Meets WireMock: External Service Mocking
Favicon
OpenAI API Mock for Devs on a Budget
Favicon
Mock S3 for AWS SDK for JavaScript (v3)
Favicon
Simplifying Kafka Testing in Python: A Mockafka-py Tutorial
Favicon
When to use Mock API?
Favicon
Datafaker: Simplifying Test Data Generation for Java and Kotlin
Favicon
NestJS: Mocking Databases for Efficient Tests
Favicon
Understanding Mocks, Stubs, and Fakes in Software Testing
Favicon
How to Typescript to JSON with Butlermock

Featured ones: