All skills
asyrafhussin avatar

/laravel-testing

@a5fe20e

Laravel 13 testing with Pest PHP 4 or PHPUnit 12. Use when writing feature tests, unit tests, or any test code in a Laravel application. Triggers on tasks involving HTTP tests, model factories, database assertions, mocking facades, authentication testing, or test organisation patterns.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/laravel-testing

This session only. Nothing lands on disk.

ruleshttp-refresh-database.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

RefreshDatabase vs DatabaseTransactions

Impact: HIGH (Prevents test pollution and ensures a clean database state between tests)

Use RefreshDatabase for most feature tests — it migrates a fresh schema and wraps each test in a transaction that rolls back on completion. Use DatabaseTransactions when you need the schema already migrated (e.g., in CI with a pre-built test database). Never share database state across tests.

Bad Example

<?php

// No database reset — tests can pollute each other
test('user count is 1 after registration', function () {
    $this->post('/register', ['name' => 'Alice', 'email' => 'a@a.com', 'password' => 'secret']);
    expect(User::count())->toBe(1); // fails if previous test left users in DB
});

// Manual teardown is fragile and forgettable
test('post is deleted', function () {
    $post = Post::factory()->create();
    $this->delete("/posts/{$post->id}");
    $post->delete(); // attempting manual cleanup — unreliable
});

Good Example

<?php

use Illuminate\Foundation\Testing\RefreshDatabase;

// Apply per-file via uses() — recommended approach in Pest
uses(RefreshDatabase::class);

test('user count is 1 after registration', function () {
    $this->post('/register', [
        'name'                  => 'Alice',
        'email'                 => 'alice@example.com',
        'password'              => 'secret123',
        'password_confirmation' => 'secret123',
    ]);

    expect(User::count())->toBe(1);
});

test('deleting a post removes it from database', function () {
    $user = User::factory()->create();
    $post = Post::factory()->for($user)->create();

    $this->actingAs($user)->delete("/posts/{$post->id}");

    $this->assertModelMissing($post);
});

Apply to entire test directory via Pest.php:

<?php

// tests/Pest.php — apply RefreshDatabase to all Feature tests globally
uses(
    Tests\TestCase::class,
    Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');

When to use DatabaseTransactions instead:

<?php

use Illuminate\Foundation\Testing\DatabaseTransactions;

// Use when: schema is pre-migrated in CI and you want faster tests
// (skips migration step, just wraps in transaction)
uses(DatabaseTransactions::class);

test('order total is calculated correctly', function () {
    $order = Order::factory()->hasItems(3)->create();
    expect($order->total)->toBeGreaterThan(0);
});

Comparison

Trait Runs Migrations Speed Use When
RefreshDatabase Yes (first run) Moderate Default for most tests
DatabaseTransactions No Faster Pre-migrated CI database
Neither No Fast No DB interaction in test

Critical: DatabaseTransactions and Nested Transactions

Do not use DatabaseTransactions when your code under test calls DB::transaction().

When the code opens its own transaction, the outer test transaction is implicitly committed — the rollback at test teardown has no effect, leaving dirty data in the database.

<?php

// Code under test:
class OrderService
{
    public function place(array $data): Order
    {
        return DB::transaction(function () use ($data) { // inner transaction
            $order = Order::create($data);
            $order->items()->create([...]);
            return $order;
        });
    }
}

// BAD — DatabaseTransactions will not clean up after a nested transaction
uses(DatabaseTransactions::class);

test('order is placed', function () {
    (new OrderService())->place([...]);
    expect(Order::count())->toBe(1); // passes but data is NOT rolled back after test
});

// GOOD — RefreshDatabase handles nested transactions correctly
uses(RefreshDatabase::class);

test('order is placed', function () {
    (new OrderService())->place([...]);
    expect(Order::count())->toBe(1); // correctly isolated
});

Why It Matters

  • Isolation: Each test starts with a clean state — no order-dependency between tests
  • Reliability: Eliminates "passes locally, fails in CI" issues caused by leftover data
  • Simplicity: No manual teardown needed — the framework handles cleanup automatically

Reference: Laravel Database Testing — Resetting the Database

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill is a comprehensive testing guide for Laravel 13, covering both Pest PHP and PHPUnit frameworks. It provides clear rules and code examples for HTTP testing, model factories, database assertions, and mocking services. No security risks or malicious patterns were identified.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer6mo

    25 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at a5fe20e. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
Other metadata
metadata
{
  "author": "Laravel Community",
  "version": "1.1.0",
  "laravelVersion": "13.x",
  "phpVersion": "8.3+",
  "pestVersion": "4.x",
  "phpunitVersion": "12.x"
}

README badge

README badge for asyrafhussin/agent-skills/laravel-testing