When performing PHP unit testing, code involving time delays (such as time_nanosleep ) often causes tests to slow down and increases execution time of CI/CD processes. Moreover, the test itself will also rely on real time, reducing the predictability and stability of the test. To improve efficiency and maintain the purity of testing, we usually need delay functions like "mock".
This article will explain how to simulate the behavior of the time_nanosleep function in unit tests in PHP without really introducing delays.
Imagine we have a service class, and the methods in this class will use time_nanosleep to perform short delay operations:
class TaskRunner
{
public function runWithDelay(): string
{
time_nanosleep(0, 500000000); // Delay 0.5 Second
return 'done';
}
}
If we test the method directly, the test will pause for half a second, which is unacceptable in a large number of test scenarios. We need to find a way to "replace" or "redefine" time_nanosleep .
PHP does not have a built-in function mock function, but we can use PHP's function search mechanism: in a namespace, if we define a function with the same name as the global function, the functions in the namespace are called first.
We encapsulate business code into a namespace, such as Gitbox\Runtime :
namespace Gitbox\Runtime;
function time_nanosleep(int $seconds, int $nanoseconds)
{
// Original behavior:Calling global functions
return \time_nanosleep($seconds, $nanoseconds);
}
class TaskRunner
{
public function runWithDelay(): string
{
time_nanosleep(0, 500000000);
return 'done';
}
}
Now, TaskRunner is using Gitbox\Runtime\time_nanosleep , which we can override in our tests.
In the test file, we define a mocked time_nanosleep function under the same namespace:
namespace Gitbox\Runtime;
function time_nanosleep(int $seconds, int $nanoseconds)
{
// 模拟Delay:Do nothing
return true;
}
Then we can test it as usual:
use Gitbox\Runtime\TaskRunner;
use PHPUnit\Framework\TestCase;
class TaskRunnerTest extends TestCase
{
public function testRunWithDelay()
{
$runner = new TaskRunner();
$result = $runner->runWithDelay();
$this->assertEquals('done', $result);
}
}
Since we override time_nanosleep in the test environment, there will be no real delay in the test.
Another more object-oriented way is to encapsulate delayed operations into a class, and by injecting dependency into the task executor, we can more flexibly simulate various behaviors, such as raising exceptions, simulating long-term blocking, etc.
interface SleeperInterface
{
public function sleep(): void;
}
class RealSleeper implements SleeperInterface
{
public function sleep(): void
{
time_nanosleep(0, 500000000);
}
}
class TaskRunner
{
private SleeperInterface $sleeper;
public function __construct(SleeperInterface $sleeper)
{
$this->sleeper = $sleeper;
}
public function runWithDelay(): string
{
$this->sleeper->sleep();
return 'done';
}
}
In the test we can use mock classes:
class FakeSleeper implements SleeperInterface
{
public function sleep(): void
{
// Do not perform any action
}
}
How to use:
$sleeper = new FakeSleeper();
$runner = new \Gitbox\Runtime\TaskRunner($sleeper);
$this->assertEquals('done', $runner->runWithDelay());
In PHP, the most efficient way to test time-dependent functions is usually by redefining the function through namespace or using dependency injection. For global functions like time_nanosleep , we recommend using namespace rewriting techniques and keeping the test logic within the controllable boundaries of the project.
In addition, using interfaces and dependency injection can make your code more flexible and testable, and suitable for more complex scenarios.
Through these methods, you can completely simulate time delays in the test without introducing real time costs, greatly improving testing efficiency and stability.
For more relevant practical examples, please visit https://gitbox.net .