Util to allow mocking PHP Internal function calls in tests.
The preferred method of installation is via Composer. Run the following command to install the latest version of a package and add it to your project's composer.json
:
composer require-dev idimsh/php-internals-mocker
This mocker is intended to be used in Unit Tests, assume a class like this:
namespace Vendor\Namespace class MyClass { public function openConnction($hostname) { return fsockopen($hostname); } }
Has to be tested with unit tests for method openConnction()
. A PhpUnit test case would be like:
namespace VendorTest\Namespace class MyClassTest extends \PHPUnit\Framework\TestCase { public function testOpenConnction(): void { $object = new \Vendor\Namespace\MyClass; $hostname = \uniqid('hostname'); $actual = $object->openConnection($hostname); // ... } }
We do not really want to open a connection especially in unit tests, so this mocker can avoid the call to the native PHP fsockopen()
and replace it with a call to a defined callback like:
namespace VendorTest\Namespace use idimsh\PhpInternalsMocker\PhpFunctionSimpleMocker; class MyClassTest extends \PHPUnit\Framework\TestCase { protected function setUp(): void { parent::setUp(); PhpFunctionSimpleMocker::reset(); } public function testOpenConnction(): void { $hostname = \uniqid('hostname'); $return = \uniqid('some mock for the return of fsockopen()'); PhpFunctionSimpleMocker::add( 'fsockopen', \Vendor\Namespace\MyClass::class, function ($inputHostname) use ($hostname, $return) { static::assertSame($inputHostname, $hostname); return $return; } ); $object = new \Vendor\Namespace\MyClass; $actual = $object->openConnection($hostname); static::assertSame($return, $actual); /** @noinspection PhpUnhandledExceptionInspection */ PhpFunctionSimpleMocker::phpUnitAssertNotEnoughCalls($this); } }
1- PhpFunctionSimpleMocker::reset()
: should be called in PhpUnit TestCase setUp()
method or at the beginning of a test method.
2- PhpFunctionSimpleMocker::add()
: to be called after reset()
to register the callbacks expected to native functions, signature:
/** * Register a call back to be called for the PHP internal function which is to be used in the class passed. * * If the $callback is null, then this PHP function is not expected to be called. * * Assertions can be done inside the callback. * * @param string $internalFunctionName The PHP function name to mock * @param string $beingCalledFromClass The class FQN which calls $internalFunctionName * @param callable|null $callback * @param int $numberOfCalls To mock more than once for the same callback, pass the number here */ public static function add( string $internalFunctionName, string $beingCalledFromClass, ?callable $callback, int $numberOfCalls = 1 ): void
It can be called multiple times with the same $internalFunctionName
and different $callback
for each call in the order expected.
The $beingCalledFromClass
expects a class FQN which from the namespace will be extracted and the function will be registered at that namespace.
3- PhpFunctionSimpleMocker::phpUnitAssertNotEnoughCalls($testCase)
: To be called from PhpUnit test method after all the assertions have been registered (last line), this method will make sure that the minimum number of calls has been reached.
4- PhpFunctionSimpleMocker::assertPostConditions(?$testCase)
: Alternative to PhpFunctionSimpleMocker::phpUnitAssertNotEnoughCalls($testCase)
and to be called from PhpUnit TestCase method: assertPostConditions()
, instead of calling the previous method at the end of each Test method, a one call passing the TestCase is enough to assert minimum count.
The native PHP function call that is to be mocked and replaced with a callback needs to be (All must apply):
- Called from a class method or a function that is defined inside a namespace and not from a class method or a function which reside in the global namespace.
- The call that PHP native function must not be preceeded by the global namespace resolution operator '\'
- The
use function
statement is not used to import that native function into the namespace in the class.
Quickly:
- PHP native functions that use references are not supported as of now, put planned to.
- In PhpUnit, assertions for not enough calls has to be explicitly handled by calling
PhpFunctionSimpleMocker::phpUnitAssertNotEnoughCalls($this)
orPhpFunctionSimpleMocker::assertPostConditions($this)
, if any better ideas are there please share. - For any strange issues, the
@runInSeparateProcess
options of PhpUnit might help, though I did not encounter such cases yet, please report if any.
- Abdulrahman Dimashki
- All Contributors
- An old Symfony class for mocking PHP Internal functions, could not find the source of it. But the code in
PhpFunctionSimpleMocker::register()
is taken from it.
There is a solution I havn't tested yet php-mock
Released under MIT License - see the License File for details.