Testing code that relies on static methods or built-in functions can be challenging in PHP. Traditionally, you’d have to refactor your code to inject dependencies or wrap functions in testable interfaces. To allow you to write clean, maintainable tests without invasive refactors, I created MintyPHP Mocking.
It allows you to write things like:
$mock = new StaticMethodMock(Adder::class, $this);
$mock->expect('add', [1, 2], 3);
$result = Adder::add(1, 2);
$mock->assertExpectationsMet();
For mocking static methods, and:
$mock = new BuiltInFunctionMock('App\Service', $this);
$mock->expect('microtime', [true], 1763333612.602);
$service = new Service();
$timestamp = $service->getCurrentTime();
$mock->assertExpectationsMet();
For mocking PHP’s built-in functions.
How it works
The library uses two techniques to achieve it’s goals:
For static methods: It intercepts class autoloading by registering a prepended autoloader. Before the real class is loaded, it creates a stub class that forwards all static calls to your expectations.
For built-in functions: It defines namespaced functions on the fly. Since PHP’s function resolution checks the current namespace first before falling back to the global namespace, unqualified calls like microtime() in your namespace will be routed through the mock.
Mocking static methods
Here’s a complete example:
use MintyPHP\Mocking\StaticMethodMock;
use App\Math\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function test_calculation(): void
{
// Register the mock before Calculator is loaded
$mock = new StaticMethodMock(Calculator::class, $this);
// Declare expectations in order
$mock->expect('multiply', [3, 4], 12);
$mock->expect('add', [12, 5], 17);
// Exercise the code under test
$result1 = Calculator::multiply(3, 4);
$result2 = Calculator::add(12, 5);
// Verify
$this->assertSame(12, $result1);
$this->assertSame(17, $result2);
$mock->assertExpectationsMet();
}
}
You can also make methods throw exceptions:
$mock->expect('divide', [10, 0], null, new \DivisionByZeroError());
Mocking built-in functions
Here’s an example mocking microtime() in a stopwatch class:
use MintyPHP\Mocking\BuiltInFunctionMock;
use App\Time\StopWatch;
use PHPUnit\Framework\TestCase;
class StopWatchTest extends TestCase
{
public function test_elapsed_time(): void
{
// Register mock for the namespace
$mock = new BuiltInFunctionMock('App\Time', $this);
// microtime(true) will be called twice
$mock->expect('microtime', [true], 1763333612.602);
$mock->expect('microtime', [true], 1763333614.825);
// Exercise the code
$sw = new StopWatch();
$sw->start();
$elapsedMs = $sw->stop();
// Verify
$this->assertSame(2223, $elapsedMs);
$mock->assertExpectationsMet();
}
}
This works for any of PHP’s built-in functions, like: microtime(), file_get_contents(), random_int() or sleep().
Important notes
- Expectations are ordered: They’re matched and consumed in FIFO order
- Arguments are compared using PHPUnit’s assertEquals: So loose comparison applies
- Call more than expected? Test fails with “No expectations left”
- Declared more than called?
assertExpectationsMet()fails - Namespace matters: Built-in function mocks only intercept unqualified calls in the specified namespace
- Fully-qualified calls bypass the mock: Calls like
\microtime()won’t be intercepted
I’ve implemented this without any dependencies (beyond PHPUnit for testing) and I’ve written comprehensive tests to ensure the code is stable and working. The library is intentionally small, explicit, and easy to reason about. Any feedback is more than welcome.
See: https://github.com/mintyphp/mocking
Enjoy!