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.

Maintainers

👁 adriansuter

Package info

github.com/adriansuter/php-autoload-override

Homepage

pkg:composer/adriansuter/php-autoload-override

Statistics

Installs: 65 742

Dependents: 12

Suggesters: 0

Stars: 22

Open Issues: 0

2.1 2026-04-22 09:12 UTC

Requires

Requires (Dev)

Suggests

None

Provides

None

Conflicts

None

Replaces

None

MIT 1adab17fec8228d71ae35d961982731a5a37e0e8

  • Adrian Suter <adrian.woop@suter-wirz.ch>

phptestingoverride

This package is auto-updated.

Last update: 2026-05-22 09:33:30 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 8.2 or later
  • Composer with PSR-4 (PSR-0 is not supported)

Installation

composer require --dev adriansuter/php-autoload-override ^2.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 uses \rand() from the global scope. Because we cannot control its output, we cannot test pick() deterministically — until we override it.

Setting up the bootstrap

Open the bootstrap script of your test suite and register the override. The recommended approach uses OverrideFactory:

// tests/bootstrap.php

use AdrianSuter\Autoload\Override\OverrideFactory;
use My\App\Probability;

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

OverrideFactory::create()
 ->forClass(Probability::class, ['rand' => \rand(...)])
 ->apply($classLoader);

Each entry in forClass() maps a function name to its real implementation, written as a first-class callable. OverrideFactory generates the override closure automatically: when a test sets a mock value via MockRegistry::set(), that value is returned; otherwise the real \rand() is called. No mock value is registered initially, so non-test code is unaffected.

For multiple classes, chain forClass() calls:

OverrideFactory::create()
 ->forClass(Clock::class, ['time' => \time(...)])
 ->forClass(Probability::class, ['rand' => \rand(...)])
 ->apply($classLoader);

If you need the raw declarations array instead (e.g. for an AbstractIntegrationTestCase), use build() instead of apply():

protected function getOverrideDeclarations(): array
{
 return OverrideFactory::create()
 ->forClass(Probability::class, ['rand' => \rand(...)])
 ->build();
}

Writing the test

Set a mock value with MockRegistry::set() before calling the code under test, and reset it in tearDown() so it does not affect other tests:

namespace My\App\Tests;

use AdrianSuter\Autoload\Override\MockRegistry;
use My\App\Probability;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class ProbabilityTest extends TestCase
{
 protected function tearDown(): void
 {
 MockRegistry::reset(Probability::class);
 }

 #[Test]
 public function pickReturnsSecondColorWhenRandExceedsProbability(): void
 {
 MockRegistry::set(Probability::class, 'rand', 35);

 $p = new Probability();
 $this->assertSame('blue', $p->pick(34, 'red', 'blue'));
 }

 #[Test]
 public function pickReturnsFirstColorWhenRandMeetsProbability(): void
 {
 MockRegistry::set(Probability::class, 'rand', 35);

 $p = new Probability();
 $this->assertSame('red', $p->pick(35, 'red', 'blue'));
 }
}

MockRegistry::reset(Probability::class) clears only the overrides for that class. Call MockRegistry::reset() without arguments to clear all registered overrides at once.

Note that these overrides are only applied during the unit tests.

Sharing an override across multiple classes

MockRegistry::set() registers an override for one specific class. To register a fallback that applies to every class, use setGlobal():

MockRegistry::setGlobal('time', 1574333284);

If a class also has a per-class override for the same function, the per-class value takes priority. Reset only the global overrides with MockRegistry::resetGlobal().

Using Override::apply() directly

If you register overrides via Override::apply() directly rather than using OverrideFactory, you write the closure yourself. MockRegistry::get() takes three arguments: the class name, the function name, and a default that is returned when no mock is registered:

Override::apply($classLoader, [
 Probability::class => [
 'rand' => function (int $min, int $max): int {
 return MockRegistry::get(Probability::class, 'rand', \rand($min, $max));
 }
 ]
]);

Be aware that the third argument — \rand($min, $max) — is evaluated on every call, even when a mock value is set. This is harmless for \rand(), but if the real function is expensive or has side effects that must be avoided when a mock is active, guard the call with MockRegistry::has():

'rand' => function (int $min, int $max): int {
 if (MockRegistry::has(Probability::class, 'rand')) {
 return MockRegistry::get(Probability::class, 'rand');
 }
 return \rand($min, $max);
}

Note: Using $GLOBALS inside override closures still works and remains fully supported. MockRegistry is a cleaner alternative, not a replacement — existing code does not need to be migrated.

Learn More

License

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