in Test Driven Development

How to test code that needs sleep?

The problem

Let’s say we have an API that knows a rate-limit of 1 request per second. When we have to make multiple requests, we need to build in some timeout mechanism. This mechanism would check if the last request was shorter than 1 second ago, otherwise it delays the request for 1 second.

One way to achieve this in PHP is:

class Api
{
    private $lastTime = 0;
    /**
     * @var \GuzzleHttp\Client
     */
    private $client;

    public function __construct(\GuzzleHttp\ClientInterface $client)
    {
        $this->client = $client;
    }

    public function doRequest(): void
    {
        $now = time();
        if ($now === $this->lastTime) {
            sleep(1);
        }
        $this->lastTime = $now;
        $this->client->get('https://some.api');
    }
}

How to test it?

To verify the behaviour of this class, we can mock the http client and do assertions on it. We are going to do a request twice and we are going to assert that the api is called twice. It would look something like this:

class ApiTest extends TestCase
{
    public function testCallsTheApi(): void
    {
        $httpClient = $this->createMock(\GuzzleHttp\ClientInterface::class);
        $httpClient->expects($this->exactly(2))
            ->method('get');

        $api = new Api($httpClient);
        $api->doRequest();
        $api->doRequest();
    }
}

Now we have a few problems. First of all we just slowed down our tests. Our unit tests are supposed to be fast, but now we waste 1 second. Second of all we are not actually asserting the second request came at least 1 second after the other. We could assert this by measuring the execution time, but this would still keep our tests slow.

A solution

To make our tests run fast and actually assert that our sleep function is called, we could do the following:

class Api
{
    private $lastTime = 0;
    /**
     * @var \GuzzleHttp\Client
     */
    private $client;
    /**
     * @var callable
     */
    private $sleep;

    public function __construct(\GuzzleHttp\ClientInterface $client, callable $sleep)
    {
        $this->client = $client;
        $this->sleep = $sleep;
    }

    public function doRequest(): void
    {
        $now = time();
        if ($now === $this->lastTime) {
            \call_user_func($this->sleep, 1);
        }

        $this->lastTime = $now;
        $this->client->get('https://some.api');
    }
}

class ApiTest extends TestCase
{
    public function testCallsTheApi(): void
    {
        $httpClient = $this->createMock(\GuzzleHttp\ClientInterface::class);
        $httpClient->expects($this->exactly(2))
            ->method('get');

        $sleepCalled = false;
        $api = new Api($httpClient, function (int $sec) use (&$sleepCalled)  {
            $sleepCalled = true;
            self::assertEquals(1, $sec);
        });
        $api->doRequest();
        $api->doRequest();

        self::assertTrue($sleepCalled);
    }
}

So what changed? We made the sleep function configurable via the constructor of the Api class. In our test we pass a spy function that asserts the callback is called and it’s called with the correct amount of seconds. This test now runs fast and is actually testing the needed timeout logic.

In our production code we would instantiate the Api class like this:

new Api(new Client(), function (int $sec) {
    sleep($sec);
});

Conclusion

There are for sure other ways to handle this case, but this is surely an elegant solution. If you have idea’s or thoughts about another solution, please let me know in the comments.

Write a Comment

Comment