New to Claude Skills? Learn how to install them →

Laffaan-m on GitHub

Laravel TDD

Free

Test-driven development for Laravel applications.

by affaan-m239.3k stars on affaan-m/ecc
2 views
Updated Aug 10, 2026
Get this skill

Free · Opens the source repo

What Laravel TDD does

The Laravel TDD skill provides a structured approach to test-driven development (TDD) using PHPUnit and Pest for Laravel applications. It emphasizes achieving over 80% test coverage through unit, feature, and integration tests. This skill guides developers through the TDD cycle of writing failing tests, implementing minimal changes to pass those tests, and refactoring code while keeping tests green. It is particularly useful for developers looking to enhance the reliability and maintainability of their Laravel applications.

The skill outlines when to use TDD, including for new features, bug fixes, and refactoring existing code. It also provides specific strategies for testing various application components, such as Eloquent models, policies, jobs, and notifications. By following the recommended testing layers—unit, feature, and integration—developers can ensure their code is well-tested across different scopes, from business logic to HTTP endpoints and database interactions.

Additionally, the skill offers detailed guidance on database testing strategies, recommending the use of RefreshDatabase for most feature and integration tests to maintain a clean state. It also discusses the choice of testing frameworks, advocating for Pest for new tests unless a project is already standardized on PHPUnit. With practical examples and best practices, this skill is designed for Laravel developers who want to integrate TDD into their workflow effectively.

When to use it

Use this skill when developing new features or endpoints in Laravel, fixing bugs, or refactoring existing code.

When not to use it

This skill may not be suitable for projects that do not require a high level of test coverage or where TDD is not part of the development process.

What you can build with it

Implementing New Features

When adding new functionalities to a Laravel application, use this skill to ensure that tests are written first, guiding the development process.

Refactoring Existing Code

If you need to refactor existing code, this skill provides a structured approach to ensure that changes do not break existing functionality.

Testing Database Interactions

Use this skill to effectively test database interactions, ensuring that your application behaves correctly with the database layer.

How to install Laravel TDD

View source

1. Install with the skills CLI

npx skills add affaan-m/ecc/laravel-tdd --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by affaan-m

Flujo de Trabajo TDD en Laravel

Desarrollo guiado por pruebas para aplicaciones Laravel usando PHPUnit y Pest con 80%+ de cobertura (unit + feature).

Cuándo Usar

  • Nuevas funcionalidades o endpoints en Laravel
  • Correcciones de bugs o refactorizaciones
  • Probar modelos Eloquent, policies, jobs y notifications
  • Preferir Pest para pruebas nuevas a menos que el proyecto ya esté estandarizado en PHPUnit

Cómo Funciona

Ciclo Rojo-Verde-Refactorizar

  1. Escribir una prueba fallida
  2. Implementar el cambio mínimo para que pase
  3. Refactorizar manteniendo las pruebas en verde

Capas de Prueba

  • Unit: clases PHP puras, objetos de valor, servicios
  • Feature: endpoints HTTP, autenticación, validación, policies
  • Integration: base de datos + colas + límites externos

Elegir capas según el alcance:

  • Usar pruebas Unit para lógica de negocio pura y servicios.
  • Usar pruebas Feature para HTTP, autenticación, validación y forma de respuesta.
  • Usar pruebas Integration cuando se validen BD/colas/servicios externos juntos.

Estrategia de Base de Datos

  • RefreshDatabase para la mayoría de pruebas feature/integration (ejecuta migraciones una vez por ejecución de prueba, luego envuelve cada prueba en una transacción cuando está soportado; las bases de datos en memoria pueden re-migrar por prueba)
  • DatabaseTransactions cuando el esquema ya está migrado y solo se necesita rollback por prueba
  • DatabaseMigrations cuando se necesita un migrate/fresh completo para cada prueba y se puede asumir el costo

Usar RefreshDatabase como predeterminado para pruebas que tocan la base de datos: para bases de datos con soporte de transacciones, ejecuta las migraciones una vez por ejecución de prueba (mediante un flag estático) y envuelve cada prueba en una transacción; para SQLite :memory: o conexiones sin transacciones, migra antes de cada prueba. Usar DatabaseTransactions cuando el esquema ya está migrado y solo se necesitan rollbacks por prueba.

Elección del Framework de Pruebas

  • Usar Pest por defecto para pruebas nuevas cuando esté disponible.
  • Usar PHPUnit solo si el proyecto ya lo estandariza o requiere herramientas específicas de PHPUnit.

Ejemplos

Ejemplo con PHPUnit

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

final class ProjectControllerTest extends TestCase
{
    use RefreshDatabase;

    public function test_owner_can_create_project(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->postJson('/api/projects', [
            'name' => 'New Project',
        ]);

        $response->assertCreated();
        $this->assertDatabaseHas('projects', ['name' => 'New Project']);
    }
}

Ejemplo de Prueba Feature (Capa HTTP)

use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

final class ProjectIndexTest extends TestCase
{
    use RefreshDatabase;

    public function test_projects_index_returns_paginated_results(): void
    {
        $user = User::factory()->create();
        Project::factory()->count(3)->for($user)->create();

        $response = $this->actingAs($user)->getJson('/api/projects');

        $response->assertOk();
        $response->assertJsonStructure(['success', 'data', 'error', 'meta']);
    }
}

Ejemplo con Pest

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

use function Pest\Laravel\actingAs;
use function Pest\Laravel\assertDatabaseHas;

uses(RefreshDatabase::class);

test('owner can create project', function () {
    $user = User::factory()->create();

    $response = actingAs($user)->postJson('/api/projects', [
        'name' => 'New Project',
    ]);

    $response->assertCreated();
    assertDatabaseHas('projects', ['name' => 'New Project']);
});

Ejemplo de Prueba Feature con Pest (Capa HTTP)

use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

use function Pest\Laravel\actingAs;

uses(RefreshDatabase::class);

test('projects index returns paginated results', function () {
    $user = User::factory()->create();
    Project::factory()->count(3)->for($user)->create();

    $response = actingAs($user)->getJson('/api/projects');

    $response->assertOk();
    $response->assertJsonStructure(['success', 'data', 'error', 'meta']);
});

Factories y Estados

  • Usar factories para datos de prueba
  • Definir estados para casos límite (archivado, admin, trial)
$user = User::factory()->state(['role' => 'admin'])->create();

Pruebas de Base de Datos

  • Usar RefreshDatabase para estado limpio
  • Mantener las pruebas aisladas y deterministas
  • Preferir assertDatabaseHas sobre consultas manuales

Ejemplo de Prueba de Persistencia

use App\Models\Project;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

final class ProjectRepositoryTest extends TestCase
{
    use RefreshDatabase;

    public function test_project_can_be_retrieved_by_slug(): void
    {
        $project = Project::factory()->create(['slug' => 'alpha']);

        $found = Project::query()->where('slug', 'alpha')->firstOrFail();

        $this->assertSame($project->id, $found->id);
    }
}

Fakes para Efectos Secundarios

  • Bus::fake() para jobs
  • Queue::fake() para trabajo en cola
  • Mail::fake() y Notification::fake() para notificaciones
  • Event::fake() para eventos de dominio
use Illuminate\Support\Facades\Queue;

Queue::fake();

dispatch(new SendOrderConfirmation($order->id));

Queue::assertPushed(SendOrderConfirmation::class);
use Illuminate\Support\Facades\Notification;

Notification::fake();

$user->notify(new InvoiceReady($invoice));

Notification::assertSentTo($user, InvoiceReady::class);

Pruebas de Autenticación (Sanctum)

use Laravel\Sanctum\Sanctum;

Sanctum::actingAs($user);

$response = $this->getJson('/api/projects');
$response->assertOk();

HTTP y Servicios Externos

  • Usar Http::fake() para aislar APIs externas
  • Verificar payloads salientes con Http::assertSent()

Objetivos de Cobertura

  • Aplicar 80%+ de cobertura para pruebas unit + feature
  • Usar pcov o XDEBUG_MODE=coverage en CI

Comandos de Prueba

  • php artisan test
  • vendor/bin/phpunit
  • vendor/bin/pest

Configuración de Pruebas

  • Usar phpunit.xml para establecer DB_CONNECTION=sqlite y DB_DATABASE=:memory: para pruebas rápidas
  • Mantener un entorno separado para pruebas para evitar tocar datos de desarrollo/producción

Pruebas de Autorización

use Illuminate\Support\Facades\Gate;

$this->assertTrue(Gate::forUser($user)->allows('update', $project));
$this->assertFalse(Gate::forUser($otherUser)->allows('update', $project));

Pruebas Feature con Inertia

Al usar Inertia.js, verificar el nombre del componente y las props con los helpers de testing de Inertia.

use App\Models\User;
use Inertia\Testing\AssertableInertia;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

final class DashboardInertiaTest extends TestCase
{
    use RefreshDatabase;

    public function test_dashboard_inertia_props(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user)->get('/dashboard');

        $response->assertOk();
        $response->assertInertia(fn (AssertableInertia $page) => $page
            ->component('Dashboard')
            ->where('user.id', $user->id)
            ->has('projects')
        );
    }
}

Preferir assertInertia sobre aserciones JSON crudas para mantener las pruebas alineadas con las respuestas de Inertia.

Frequently asked questions about Laravel TDD

Similar skills