HTTP and Database Assertions

+15 Mana ✨

Introduction

Laravel gives you an expressive set of assertions for every layer of an HTTP response and every row in your test database. Mastering a handful of them is enough to cover the vast majority of feature tests.

Key Concepts

  • Response assertions: Methods on the TestResponse object returned by get(), post(), etc.
  • Database assertions: Global helpers that query your test database directly — assertDatabaseHas, assertDatabaseMissing, assertDatabaseCount.
  • Chainable API: All response assertions return $this, so you can chain them into a single readable block.

Real World Context

When an endpoint changes, one clear assertion failure — Expected 201, got 422 — tells you exactly what broke. Vague assertTrue(true) noise tells you nothing. The right assertion is half the battle.

Deep Dive

Response Assertions

Chain assertions to check status, content, and session state in one readable block:

php
use function Pest\Laravel\{get, post};

it('returns JSON for the projects API', function () {
    $response = get('/api/projects');

    $response
        ->assertStatus(200)
        ->assertHeader('Content-Type', 'application/json')
        ->assertJsonCount(3, 'data')
        ->assertJsonStructure([
            'data' => [
                '*' => ['id', 'name', 'status'],
            ],
        ]);
});

it('redirects after storing a project', function () {
    post('/projects', ['name' => 'New'])
        ->assertRedirect('/projects')
        ->assertSessionHas('success', 'Project created!');
});

Common response assertions you'll reach for constantly:

AssertionChecks
assertStatus(200)HTTP status code
assertOk()Shortcut for status 200
assertRedirect('/path')Response redirects to /path
assertSee('text')Response body contains the text
assertDontSee('error')Response body does NOT contain the text
assertJson([...])Response JSON contains the given fragment
assertJsonStructure([...])Response JSON matches the given shape
assertSessionHasErrors([...])Session contains validation errors

Database Assertions

php
use function Pest\Laravel\{assertDatabaseHas, assertDatabaseMissing, assertDatabaseCount};

it('persists the project', function () {
    post('/projects', ['name' => 'Launch Website']);

    assertDatabaseHas('projects', ['name' => 'Launch Website']);
    assertDatabaseCount('projects', 1);
});

assertDatabaseHas queries the database directly — perfect for verifying side effects that the response body does not reveal.

Common Pitfalls

  1. Asserting on implementation details — assertDatabaseHas is fine, but assertSee on CSS class names is brittle. Assert on user-visible text or API shape, not DOM internals.
  2. Skipping RefreshDatabase — Flaky tests usually come from leftover data. Always isolate state.

Best Practices

  1. Chain assertions — assertStatus(200)->assertSee('Welcome') reads top-to-bottom like a spec.
  2. Use assertJsonStructure for APIs — It locks the contract's shape without tying you to exact values.

Summary

  • Response assertions cover status, headers, body, session, and JSON shape.
  • Database assertions verify rows exist, are missing, or match a count.
  • Chain assertions into a single readable block.
  • Assert on behavior and shape, not implementation details.

Code Examples

php
<?php

use App\Models\Project;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\{get, assertDatabaseHas};

uses(RefreshDatabase::class);

it('lists existing projects', function () {
    Project::factory()->create(['name' => 'Launch Website']);
    Project::factory()->create(['name' => 'Hire Designer']);

    get('/projects')
        ->assertOk()
        ->assertSee('Launch Website')
        ->assertSee('Hire Designer');

    assertDatabaseHas('projects', ['name' => 'Launch Website']);
});
✓ Completed