idimsh/php-internals-mocker

Helps mocking PHP internal functions calls

v1.0.0 2019-12-03 21:33 UTC

This package is auto-updated.

Last update: 2024-04-22 05:37:25 UTC


README

Latest Version on Packagist Software License Build Status Coverage Status Quality Score Total Downloads PHP Version

Util to allow mocking PHP Internal function calls in tests.

Installation

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

Usage

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

Methods Manual

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.

Usage Conditions

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.

Limitations

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) or PhpFunctionSimpleMocker::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.

Credits

Alternatives

There is a solution I havn't tested yet php-mock

License

Released under MIT License - see the License File for details.