Laravel cookbook · Beginner
Write feature tests with fakes
Tests hit real queues and mail servers, or only test happy paths.
Approach
Use RefreshDatabase, model factories, actingAs() and fakes for queues, mail and HTTP. Assert on status codes,
JSON, database state and dispatched jobs, including authorization failures.
Solution
<?php
// tests/Feature/PlaceOrderTest.php
namespace Tests\Feature;
use App\Jobs\SyncInvoiceToAccounting;
use App\Models\Product;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Tests\TestCase;
class PlaceOrderTest extends TestCase
{
use RefreshDatabase;
public function test_customer_can_place_an_order(): void
{
Queue::fake();
$user = User::factory()->create();
$product = Product::factory()->create(['stock' => 3, 'price_cents' => 2400]);
$this->actingAs($user)
->postJson('/api/orders', ['product_id' => $product->id, 'quantity' => 2, 'shipping' => ['country' => 'DE']])
->assertCreated()
->assertJsonPath('data.total', 48);
$this->assertDatabaseHas('products', ['id' => $product->id, 'stock' => 1]);
Queue::assertPushed(SyncInvoiceToAccounting::class);
}
public function test_quantity_is_validated(): void
{
$this->actingAs(User::factory()->create())
->postJson('/api/orders', ['product_id' => 999, 'quantity' => 50])
->assertUnprocessable()
->assertJsonValidationErrors(['product_id', 'quantity', 'shipping.country']);
}
public function test_guests_cannot_order(): void
{
$this->postJson('/api/orders', [])->assertUnauthorized();
}
}
Pitfalls
- Faking everything, including the database, so tests pass while queries fail in production.
- Only testing as an admin; test as regular users and other tenants.
- Factories creating huge object graphs by default; keep them minimal with states.
Variations
- Pest offers the same assertions with a terser syntax (
it('places an order', function () { ... })). - Run
php artisan test --parallelin CI.
Further reading: HTTP tests
Reference: https://laravel.com/docs/13.x/http-tests
More Laravel recipes
- Authorize actions with policies
- Cache expensive data with stale-while-revalidate
- Call external APIs with timeouts, retries and fakes
- Catch N+1 queries with strict Eloquent mode
- Delete old records automatically with prunable models
- Dispatch events and listeners after the transaction commits
- Issue scoped API tokens with Sanctum
- Make queued jobs reliable with retries, backoff and uniqueness