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.