Before you write an assertion, answer one question: what does the consumer of this endpoint depend on? Not what the controller does internally. Not which method was called. The consumer sees three things: the status code, the shape of the JSON body, and whatever side effect they were promised. Test those. Nothing else survives a refactor.
// tests/Feature/LicenseTest.php
it('lists licenses', function () {
actingAsConsumer('licenses:read');
fakeProvider([[
'key' => 'lic_1',
'name' => 'Acme',
'domains' => ['acme.test'],
'created_at' => '2026-01-29T12:00:00Z',
]]);
$this->getJson('/api/licenses')
->assertOk()
->assertJsonPath('data.0.key', 'lic_1')
->assertJsonStructure(['data' => [
['key', 'name', 'domains', 'created_at'],
]]);
});
This test doesn’t check which private method ran or what the driver was called with. It hits the route the way a consumer would and checks the response the way a consumer would: status first, then content. Swap the whole integration behind Licenses::all() tomorrow and this test doesn’t care, as long as /api/licenses still answers in the documented shape.
Compare it with a test that asserts on a collaborator:
// Bad: proves wiring fired, not that a client was answered
it('creates a license', function () {
actingAsConsumer('licenses:write');
$this->mock(LicenseContract::class)
->shouldReceive('create')->once();
$this->postJson('/api/licenses', [
'name' => 'Acme',
'domain' => 'acme.test',
]);
});
That version breaks the moment you rename a method or move a step into a job, and it passes even if the endpoint returns a 500. A test should break when the contract changes, which is exactly when you want to know, and at no other time.
Replacing the contract is still useful, as Chapter 6 did, when the provider isn’t what the test is about. Use it to get the provider out of the way, not as the assertion.
Feature Tests Over Unit Tests
Almost all of an API’s test suite should be feature tests.
A unit test proves a function works in isolation. An API doesn’t ship isolated functions. It ships a request that threads through authentication, the rate limiter, validation, a facade, an external call, and a Resource. Unit-test any one of those and you have proven nothing about whether the chain works. Bugs in a Laravel application live at the seams between components, and a feature test is the only kind that crosses them.
The unit tests that do exist earn their place. License::fromProvider() in Chapter 3 has a real decision in it and needs no request to reach it, so it has two unit tests. That is the bar: if a test needs getJson() to prove anything, it is a feature test. If one method call and an assertion are enough, it is a unit test.