When your code contains sleep() calls, tests have to wait too. For example, this retry logic makes each test run take 15 seconds:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // app/Http/Controllers/HomeController.php
namespace App\Http\Controllers;
class HomeController extends Controller
{
public function index(): string
{
$tries = 3;
while (true) {
// do something
sleep(5);
$tries--;
if ($tries <= 0) {
break;
}
}
return 'foo';
}
}
|
Approach 1: Extract a Clock Class and Mock It
Wrap sleep in a Clock class and replace it with a Mockery spy in tests.
1
2
3
4
5
6
7
8
9
| namespace App;
class Clock
{
public function sleep(int $second): void
{
sleep($second);
}
}
|
Inject Clock into the controller:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // app/Http/Controllers/HomeController.php
namespace App\Http\Controllers;
use App\Clock;
class HomeController extends Controller
{
public function index(Clock $clock): string
{
$tries = 3;
while (true) {
// do something
$clock->sleep(5);
$tries--;
if ($tries <= 0) {
break;
}
}
return 'foo';
}
}
|
Swap it in the test:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| namespace Tests\Feature;
use App\Clock;
use Mockery as m;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_sleep(): void
{
$clock = m::spy(Clock::class);
$this->swap(Clock::class, $clock);
$this->get('/')
->assertOk()
->assertSee('foo');
// verify sleep was called the expected number of times
$clock->shouldHaveReceived('sleep')->times(3);
}
}
|
The advantage of this approach is that you can use the spy to verify how many times sleep was called.
Approach 2: Mock the Built-in Function with php-mock
If you don’t want to modify the original code, use php-mock. The controller stays untouched — only the test changes:
1
| composer require --dev php-mock/php-mock
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| namespace Tests\Feature;
use phpmock\environment\SleepEnvironmentBuilder;
use Tests\TestCase;
class ExampleTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
$builder = new SleepEnvironmentBuilder();
// namespace where sleep() is called
$builder->addNamespace('App\Http\Controllers');
$this->environment = $builder->build();
$this->environment->enable();
}
protected function tearDown(): void
{
parent::tearDown();
$this->environment->disable();
}
public function test_sleep(): void
{
$this->get('/')
->assertOk()
->assertSee('foo');
}
}
|
php-mock has a limitation: sleep must be called within a namespace to be mockable. If sleep is called in the global namespace, this approach won’t work.