adriansuter/php-autoload-override

Override global scoped fully qualified function calls inside your class methods in order to be able to mock them during testing.

1.4 2022-11-09 08:46 UTC

This package is auto-updated.

Last update: 2024-03-09 11:43:05 UTC


README

Build Status Coverage Status Total Downloads License

This library allows overriding fully qualified function calls inside your class methods in order to be able to mock them during testing.

NOTE: The library can be used for other scenarios as well. But we recommend using it for testing purposes only.

PHP-Autoload-Override Website

Requirements

  • PHP 7.3 or later
  • Composer with PSR-4 (PSR-0 is not supported)

Installation

$ composer require --dev adriansuter/php-autoload-override 1.0

Usage with PHPUnit

Say we want to unit test the following class Probability.

namespace My\App;

class Probability
{
    public function pick(int $probability, string $color1, string $color2): string
    {
        if (\rand(1, 100) <= $probability) {
            return $color1;
        } else {
            return $color2;
        }
    }
}

The class has one method pick that takes a probability (between 0 and 100) and two color names as arguments. The method would then use the rand function of the global scope to generate a random number and if the generated number is smaller equal to the given probability, then the method would return the first color, otherwise the method would return the second color.

The problem

As we cannot control the output of the rand function (it is in global scope), we cannot unit test that method. Well, until now. Using the PHP-Autoload-Override library, it is possible to override the rand function and therefore control its generated random number.

The solution

After installing the PHP-Autoload-Override library, we would open the bootstrap script of our test suite (see also PHPUnit Configuration). There we will write the following code

// tests/bootstrap.php

/** @var \Composer\Autoload\ClassLoader $classLoader */
$classLoader = require_once __DIR__ . '/../vendor/autoload.php';

\AdrianSuter\Autoload\Override\Override::apply($classLoader, [
    \My\App\Probability::class => [
        'rand' => function ($min, $max): int {
            if (isset($GLOBALS['rand_return'])) {
                return $GLOBALS['rand_return'];
            }

            return \rand($min, $max);
        }
    ]
]);

Now the class Probability would be loaded into the PHPUnit runtime such that all function calls to the global scoped rand() function in the class Probability get overridden by the closure given above.

Our test class can now be written as follows.

namespace My\App\Tests;

use My\App\Probability;
use PHPUnit\Framework\TestCase;

final class ProbabilityTest extends TestCase
{
    protected function tearDown()
    {
        if (isset($GLOBALS['rand_return'])) {
            unset($GLOBALS['rand_return']);
        }
    }

    public function testPick()
    {
        $p = new Probability();

        $GLOBALS['rand_return'] = 35;

        $this->assertEquals('blue', $p->pick(34, 'red', 'blue'));
        $this->assertEquals('red', $p->pick(35, 'red', 'blue'));
    }
}

The test case testPick would call the pick method two times. As we have overridden the \rand function, we can control its returned value to be always 35. So the first call checks, if the else-block gets executed. The second one checks, if the if-block gets executed. Hooray, 100% code coverage.

Note that this override would only be applied during the unit tests.

Learn More

License

The PHP-Autoload-Override library is licensed under the MIT license. See License File for more information.