Function Mocker 1.2.1 out!

A much needed refresh of the “necessary evil” library.

Monkey patching (thanks Patchwork)

The term “monkey patching”, when applied to code, refers to:

[…] changing code sneakily – and possibly incompatibly with other such patches – at runtime.

Source: WikiPedia.
The adverbs used in the sentence above, “sneakily” and “possibly incompatibly”, can be daunting.
But, exactly as mocking engines are based on the use of the dreaded PHP eval function for a good reason and a good cause, monkey patching should be regarded and treated in the same way.
The Patchwork library offers “userland” monkey patching for PHP code to allow redefining both internal and user defined functions and static methods.
The “userland” part refers to the fact that the library does not require the runkit extension to work.
While the library was born for testing purposes, and its API lends itself to it, I wanted to have something I could use in my WordPress tests with PhpUnit, Codeception, or wp-browser in a way similar to any other mocking engine.
So it was born, on the shoulders of Patchwork, function-mocker.

Unlawful mocking, stubbing and spying

Since I’ve just updated the library to support the latest possibilities offered by Patchwork, I thought it would be nice to show what it can do.
I’m assuming here a PhpUnit like test case containing test methods that could run in a vanilla PhpUnit setup, a Codeception, and a wp-browser powered one.
How could mocking internal PHP functions come in handy? A test is worth a thousand words:

<?php

// FunctionMocker needs the functions to be defined to replace them
function get_option($option) { // no-op } function update_option($option, $value) { // no-op } // The class under test class Logger { public function log($type, $message) {$option = get_option('log');
$option[] = sprintf('[%s] %s - %s', date(DATE_ATOM, time()),$type, $message); update_option('log', sprintf('[%s] %s - %s', date(DATE_ATOM, time()),$type, $message)); } } class InternaFunctionReplacementTest extends \PHPUnit\Framework\TestCase { /** * It should log the correct message * @test */ public function log_the_correct_message() {$mockTime = time();
\tad\FunctionMocker\FunctionMocker::replace('time', $mockTime); \tad\FunctionMocker\FunctionMocker::replace('get_option', []);$update_option = \tad\FunctionMocker\FunctionMocker::replace('update_option');

$logger = new Logger();$logger->log('error', 'There was an error');

$expected = sprintf('[%s] error - There was an error', date(DATE_ATOM,$mockTime));
$update_option->wasCalledWithOnce(['log',$expected]);
}
}

There are three feats performed here by function-mocker:

1. replacing the time internal function in the line

$mockTime = time(); \tad\FunctionMocker\FunctionMocker::replace('time',$mockTime);

To allow for that, I’ve made the time function redefineable when initializing FunctionMocker in the tests bootstrap file:

<?php

'redefinable-internals' => ['time']
]);
2. replacing the get_option function to make it return a value:

\tad\FunctionMocker\FunctionMocker::replace('get_option', []);
3. replacing and spying the update_option function:

$update_option = \tad\FunctionMocker\FunctionMocker::replace('update_option'); [...]$update_option->wasCalledWithOnce(['log', \$expected]);