Testing
Never call SnelStart from a test: it is somebody’s bookkeeping. The Laravel client sends everything through Laravel’s HTTP client, so Http::fake() (which answers requests without sending them) covers it.
A complete test
This tests the controller of the Quick start. The client makes two kinds of requests, to two hosts: the token endpoint and the API. Fake both, and block everything else.
tests/Feature/SnelstartCompanyTest.php
<?php
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
beforeEach(function () {
// A request that no fake answers fails the test instead of reaching SnelStart.
Http::preventStrayRequests();
// Without a client key the client throws the moment it is built.
config(['snelstart.client_key' => 'test-client-key']);
});
it('shows the company info', function () {
Http::fake([
'auth.snelstart.nl/*' => Http::response(['access_token' => 'test-token', 'expires_in' => 3600]),
'b2bapi.snelstart.nl/v2/companyInfo' => Http::response(['naam' => 'Example B.V.']),
]);
$this->get('/snelstart/company')
->assertOk()
->assertJson(['naam' => 'Example B.V.']);
Http::assertSent(fn ($request) => $request->url() === 'https://b2bapi.snelstart.nl/v2/companyInfo'
&& $request->hasHeader('Authorization', 'Bearer test-token'));
});
it('says so when SnelStart refuses the call', function () {
Http::fake([
'auth.snelstart.nl/*' => Http::response(['access_token' => 'test-token']),
'b2bapi.snelstart.nl/*' => Http::response(['message' => 'Too many requests'], 429),
]);
$this->get('/snelstart/company')
->assertStatus(502)
->assertJson(['error' => 'Too many calls, try again later.']);
});
it('says so when SnelStart does not answer', function () {
Http::fake([
'auth.snelstart.nl/*' => Http::response(['access_token' => 'test-token']),
'b2bapi.snelstart.nl/*' => fn () => throw new ConnectionException('cURL error 28: Operation timed out'),
]);
$this->get('/snelstart/company')->assertStatus(504);
});
The example is written for Pest. The first test proves the token was fetched and sent; the second and third prove what your user sees when SnelStart refuses or is silent. The response bodies in the fakes are made up for the test: the package does not care what is in them.
Two things to remember:
- Set
snelstart.client_keybefore the client is used, asbeforeEachdoes. - The client is a singleton that reads the config once. After changing the config inside a test, call
app()->forgetInstance(\Darvis\Snelstart\Services\SnelstartAPI::class).
The cached token
The client keeps its token in the cache. With the array cache store, which the phpunit.xml of a new Laravel application sets with CACHE_STORE=array, every test starts without a token and nothing changes for you.
When your tests run on a store that persists (file, Redis, database), the token of one test is still there in the next: the token endpoint is not called again, so an Http::assertSentCount() is one lower than you expect, and a fake that returns another token is ignored. Pick one:
<env name="SNELSTART_TOKEN_CACHE" value="false"/>
use Illuminate\Support\Facades\Cache;
beforeEach(fn () => Cache::flush());
The first goes in phpunit.xml, inside <php>; the second in the test file or in tests/Pest.php. Inside a test, app(SnelstartAPI::class)->forgetToken() drops the token, in memory and in the cache.
Assert on the request a write sends
Http::assertSent(function ($request) {
return $request->method() === 'POST'
&& $request->url() === 'https://b2bapi.snelstart.nl/v2/relaties'
&& $request['naam'] === 'Example B.V.'
&& $request->hasHeader('Authorization', 'Bearer test-token');
});
Assert on the status of a failure
When you test a class of your own instead of a URL, assert on the exception and its status:
use Darvis\Snelstart\Exceptions\SnelstartException;
use Darvis\Snelstart\Services\SnelstartAPI;
use Illuminate\Support\Facades\Http;
it('gets a 429 as a SnelstartException', function () {
Http::fake([
'auth.snelstart.nl/*' => Http::response(['access_token' => 'test-token']),
'b2bapi.snelstart.nl/*' => Http::response(['message' => 'Too many requests'], 429),
]);
try {
app(SnelstartAPI::class)->getRelaties();
$this->fail('Expected a SnelstartException.');
} catch (SnelstartException $e) {
expect($e->status())->toBe(429)
->and($e->getMessage())->toContain('HTTP status: 429');
}
});
SnelstartException extends RuntimeException, so an assertion on RuntimeException::class you already have still passes.
An expired token
The Laravel client uses the application clock, so travel in time. With the fakes of the first test in place:
$api = app(SnelstartAPI::class);
$api->getCompanyInfo();
$this->travel(1)->hours();
$api->getCompanyInfo();
Http::assertSentCount(4); // two token requests, two API calls
A refused token
A 401 on a token the client already had gives one new token and one repeat. A fake that always answers 401 therefore sees the call twice when an earlier call in the same test succeeded, and once when it is the first call:
$accepted = 'first-token';
$issued = 0;
Http::fake([
'auth.snelstart.nl/*' => function () use (&$issued) {
return Http::response(['access_token' => ++$issued === 1 ? 'first-token' : 'second-token']);
},
'b2bapi.snelstart.nl/*' => function ($request) use (&$accepted) {
return $request->hasHeader('Authorization', 'Bearer '.$accepted)
? Http::response(['id' => 'r1'])
: Http::response([], 401);
},
]);
$api = app(SnelstartAPI::class);
$api->getCompanyInfo();
$accepted = 'second-token'; // from now on the first token is refused
expect($api->getCompanyInfo())->toBe(['id' => 'r1']);
Why Http::fake() does not cover the standalone client
It uses cURL directly, so Http::fake() does not see it and Http::preventStrayRequests() does not stop it. In a Laravel application, use the Laravel client. The package tests the standalone client against a small server on 127.0.0.1, see tests/Fixtures/standalone-server.php in the repository.