adriansuter/php-autoload-override
Override fully qualified function calls inside PHP class methods so you can mock them in tests. Works with Composer PSR-4 autoloading (PHP 8.2+). Use OverrideFactory to map functions (e.g., rand) to custom implementations for deterministic PHPUnit tests.
composer require --dev adriansuter/php-autoload-override
bootstrap/app.php (or a TestingServiceProvider):
use AdrianSuter\Autoload\Override\OverrideFactory;
$app->booting(function () {
OverrideFactory::create()
->forClass(\App\Services\RandomService::class, ['rand' => \rand(...)])
->apply($app->get('composer.autoload.classloader'));
});
MockRegistry in tests to override functions:
use AdrianSuter\Autoload\Override\MockRegistry;
public function testRandomService()
{
MockRegistry::set(\App\Services\RandomService::class, 'rand', 42);
$result = (new \App\Services\RandomService())->generate();
$this->assertEquals(42, $result);
}
\rand() in a Service Class// app/Services/RandomService.php
class RandomService {
public function generate(): int
{
return \rand(1, 100);
}
}
Test:
public function testGenerateReturnsMockedValue()
{
MockRegistry::set(RandomService::class, 'rand', 99);
$this->assertEquals(99, (new RandomService())->generate());
}
Use a TestingServiceProvider to manage overrides globally:
// app/Providers/TestingServiceProvider.php
public function boot()
{
$this->app->afterResolving('composer.autoload.classloader', function ($loader) {
OverrideFactory::create()
->forClass(\App\Models\User::class, ['now' => \Carbon\Carbon::now()])
->forClass(\App\Services\PaymentService::class, ['time' => \time()])
->apply($loader);
});
}
Create a reusable trait for test classes:
// tests/Traits/OverrideTrait.php
trait OverrideTrait {
protected function setOverride(string $class, string $function, $value): void
{
MockRegistry::set($class, $function, $value);
}
protected function resetOverride(string $class): void
{
MockRegistry::reset($class);
}
}
Usage:
use Tests\Traits\OverrideTrait;
class PaymentServiceTest extends TestCase {
use OverrideTrait;
public function testPaymentProcessing()
{
$this->setOverride(PaymentService::class, 'time', 1625097600);
// Test logic...
}
protected function tearDown(): void
{
$this->resetOverride(PaymentService::class);
parent::tearDown();
}
}
Override functions globally (e.g., for all tests):
// tests/bootstrap.php
MockRegistry::setGlobal('time', \time());
MockRegistry::setGlobal('rand', function () { return 42; });
Override facade methods (e.g., Str::random()):
// bootstrap/app.php
OverrideFactory::create()
->forClass(\Illuminate\Support\Str::class, ['random' => fn () => 'mocked-random-string'])
->apply($loader);
Use factories to generate test data with mocked functions:
// tests/CreatesMockedUsers.php
use AdrianSuter\Autoload\Override\MockRegistry;
trait CreatesMockedUsers {
public function createMockedUser(): User
{
MockRegistry::set(User::class, 'str_random', 'user-123');
return User::factory()->create();
}
}
Class Loading Timing:
bootstrap/app.php or a service provider’s boot() method.app()->booted() to delay override application:
$app->booted(function () {
OverrideFactory::create()->forClass(...)->apply($loader);
});
OPcache Invalidation:
phpunit.xml.dist:
<php>
<ini name="opcache.enable" value="0"/>
</php>
bootstrap/app.php:
if (function_exists('opcache_reset')) {
opcache_reset();
}
Namespace Collisions:
App\) can affect unrelated classes.\App\Helpers\str() may break Str::random() if not scoped correctly.Static Analysis Tools:
@phpstan-ignore-next-line or configure your static analyzer to ignore the adriansuter/php-autoload-override namespace.Performance in Non-Test Environments:
tests/bootstrap.php) and ensure overrides are not applied in production.MockRegistry Scope Leaks:
MockRegistry::reset() can cause test pollution.tearDown() or a trait to auto-reset:
protected function tearDown(): void
{
MockRegistry::reset(static::class);
parent::tearDown();
}
Verify Overrides Are Applied:
// Check if an override exists
if (MockRegistry::has(\App\Services\RandomService::class, 'rand')) {
echo "Override is set!";
}
Log Override Declarations:
$declarations = OverrideFactory::create()
->forClass(...)
->build();
\Log::debug('Override declarations:', $declarations);
Test with MockRegistry::get():
$mockedValue = MockRegistry::get(\App\Services\RandomService::class, 'rand');
$this->assertEquals(42, $mockedValue);
Check Autoloader Modifications:
$loader = $app->get('composer.autoload.classloader');
\Log::debug('Autoloader prefixes:', $loader->getPrefixes());
Custom Override Logic:
Override the MockRegistry to add logging or validation:
MockRegistry::extend(function ($class, $function, $value) {
\Log::debug("Setting override for {$class}::{$function} = {$value}");
return $value;
});
Dynamic Overrides via Config:
Load overrides from config/testing.php:
// config/testing.php
'overrides' => [
\App\Services\RandomService::class => ['rand' => 42],
],
// TestingServiceProvider
public function boot()
{
collect(config('testing.overrides'))
->each(fn ($functions, $class) =>
OverrideFactory::create()->forClass($class, $functions)
)
->first()
->apply($loader);
}
Integration with Laravel’s Testing Helper:
Extend Laravel’s Testing trait to auto-apply overrides:
// app/Traits/WithMockedFunctions.php
use AdrianSuter\Autoload\Override\{OverrideFactory, MockRegistry};
trait WithMockedFunctions {
public function withMockedFunctions(array $overrides): static
{
OverrideFactory::create()
->forClass(static::class, $overrides)
->apply($this->app->get('composer.autoload.classloader'));
return $this;
}
}
Usage:
$this->withMockedFunctions(['rand' => 42])->get('/random');
**Support for Non-PSR
How can I help you explore Laravel packages today?