/laravel-tdd
Laravel testing strategies with PHPUnit, Pest, model factories, HTTP tests, Sanctum authentication testing, mocking, and coverage.
$ npx -y skills add affaan-m/ECC --skill laravel-tdd --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/laravel-tdd
Context preview
The summary Claude sees to decide when to auto-load this skill.
Laravel testing strategies with PHPUnit, Pest, model factories, HTTP tests, Sanctum authentication testing, mocking, and coverage.
SKILL.md
laravel-tdd.SKILL.mdname: laravel-tdd
description: Laravel testing strategies with PHPUnit, Pest, model factories, HTTP tests, Sanctum authentication testing, mocking, and coverage.
metadata:
origin: ECC
Laravel Testing with TDD
Test-driven development for Laravel applications using PHPUnit, Pest, Laravel factories, and testing helpers.
When to Activate
- Writing new Laravel applications or features
- Implementing API endpoints with Sanctum or Passport authentication
- Testing Eloquent models, relationships, scopes, and accessors
- Setting up testing infrastructure for Laravel projects
- Writing feature tests for HTTP controllers and form requests
- Mocking external services (queues, mail, notifications, HTTP)
TDD Workflow for Laravel
Red-Green-Refactor Cycle
// Step 1: RED — Write a failing test
public function test_a_product_can_be_created(): void
{
$product = Product::factory()->create(['name' => 'Test Product']);
$this->assertDatabaseHas('products', ['name' => 'Test Product']);
}
// Step 2: GREEN — Write the migration, model, and factory
// Step 3: REFACTOR — Improve while keeping tests greenSetup
PHPUnit Configuration
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">tests/Feature</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>Base TestCase Setup
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
parent::setUp();
// Call $this->withoutExceptionHandling() only in tests that
// test non-HTTP exceptions; it suppresses assertStatus() etc.
}
// Helper: Authenticate and return user
protected function actingAsUser(): mixed
{
$user = \App\Models\User::factory()->create();
$this->actingAs($user);
return $user;
}
protected function actingAsAdmin(): mixed
{
$admin = \App\Models\User::factory()->admin()->create();
$this->actingAs($admin);
return $admin;
}
}Model Factories
// database/factories/UserFactory.php
class UserFactory extends Factory
{
protected static ?string $password = null;
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
'role' => 'user',
];
}
public function admin(): static
{
return $this->state(fn (array $attributes) => ['role' => 'admin']);
}
public function unverified(): static
{
return $this->state(fn (array $attributes) => ['email_verified_at' => null]);
}
}
// database/factories/ProductFactory.php
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->unique()->words(3, true),
'slug' => fn (array $attrs) => Str::slug($attrs['name']),
'description' => fake()->paragraph(),
'price' => fake()->numberBetween(100, 100000),
'stock' => fake()->numberBetween(0, 100),
'is_active' => true,
'user_id' => UserFactory::new(),
];
}
public function outOfStock(): static
{
return $this->state(fn (array $attributes) => ['stock' => 0]);
}
}Using Factories
$user = User::factory()->create();
$admin = User::factory()->admin()->create();
$product = Product::factory()->create(['user_id' => $user->id]);
$products = Product::factory()->count(10)->create();
$draft = Product::factory()->make(); // Not persisted
// With relationships
$user = User::factory()->has(Product::factory()->count(3))->create();
// Sequences
User::factory()->count(3)->sequence(
['role' => 'admin'], ['role' => 'editor'], ['role' => 'user'],
)->create();Model Testing
namespace Tests\Unit\Models;
use App\Models\User;
use App\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
public function test_it_hides_sensitive_attributes(): void
{
$user = User::factory()->create();
$this->assertArrayNotHasKey('password', $user->toArray());
}
public function test_admin_scope_returns_only_admins(): void
{
User::factory()->admin()->create();
User::factory()->count(3)->create();
$this->assertCount(1, User::admin()->get());
}
}
class ProductTest extends TestCase
{
use RefreshDatabase;
public function test_active_scope_filters_correctly(): void
{
Product::factory()->count(3)->create(['is_active' => true]);
Product::factory()->count(2)->create(['is_active' => false]);
$this->assertCount(3, Product::active()->get());
}
public function test_it_belongs_to_a_user(): void
{
$user = User::factory()->create();
$product = Product::factory()->create(['user_id' => $usRead more
name: laravel-tdd description: Laravel testing strategies with PHPUnit, Pest, model factories, HTTP tests, Sanctum authentication testing, mocking, and coverage. metadata: origin: ECC
Laravel Testing with TDD
Test-driven development for Laravel applications using PHPUnit, Pest, Laravel factories, and testing helpers.
When to Activate
- Writing new Laravel applications or features
- Implementing API endpoints with Sanctum or Passport authentication
- Testing Eloquent models, relationships, scopes, and accessors
- Setting up testing infrastructure for Laravel projects
- Writing feature tests for HTTP controllers and form requests
- Mocking external services (queues, mail, notifications, HTTP)
TDD Workflow for Laravel
Red-Green-Refactor Cycle
// Step 1: RED — Write a failing test
public function test_a_product_can_be_created(): void
{
$product = Product::factory()->create(['name' => 'Test Product']);
$this->assertDatabaseHas('products', ['name' => 'Test Product']);
}
// Step 2: GREEN — Write the migration, model, and factory
// Step 3: REFACTOR — Improve while keeping tests greenSetup
PHPUnit Configuration
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">tests/Feature</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_STORE" value="array"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>Base TestCase Setup
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
parent::setUp();
// Call $this->withoutExceptionHandling() only in tests that
// test non-HTTP exceptions; it suppresses assertStatus() etc.
}
// Helper: Authenticate and return user
protected function actingAsUser(): mixed
{
$user = \App\Models\User::factory()->create();
$this->actingAs($user);
return $user;
}
protected function actingAsAdmin(): mixed
{
$admin = \App\Models\User::factory()->admin()->create();
$this->actingAs($admin);
return $admin;
}
}Model Factories
// database/factories/UserFactory.php
class UserFactory extends Factory
{
protected static ?string $password = null;
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
'role' => 'user',
];
}
public function admin(): static
{
return $this->state(fn (array $attributes) => ['role' => 'admin']);
}
public function unverified(): static
{
return $this->state(fn (array $attributes) => ['email_verified_at' => null]);
}
}
// database/factories/ProductFactory.php
class ProductFactory extends Factory
{
public function definition(): array
{
return [
'name' => fake()->unique()->words(3, true),
'slug' => fn (array $attrs) => Str::slug($attrs['name']),
'description' => fake()->paragraph(),
'price' => fake()->numberBetween(100, 100000),
'stock' => fake()->numberBetween(0, 100),
'is_active' => true,
'user_id' => UserFactory::new(),
];
}
public function outOfStock(): static
{
return $this->state(fn (array $attributes) => ['stock' => 0]);
}
}Using Factories
$user = User::factory()->create();
$admin = User::factory()->admin()->create();
$product = Product::factory()->create(['user_id' => $user->id]);
$products = Product::factory()->count(10)->create();
$draft = Product::factory()->make(); // Not persisted
// With relationships
$user = User::factory()->has(Product::factory()->count(3))->create();
// Sequences
User::factory()->count(3)->sequence(
['role' => 'admin'], ['role' => 'editor'], ['role' => 'user'],
)->create();Model Testing
namespace Tests\Unit\Models;
use App\Models\User;
use App\Models\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class UserTest extends TestCase
{
use RefreshDatabase;
public function test_it_hides_sensitive_attributes(): void
{
$user = User::factory()->create();
$this->assertArrayNotHasKey('password', $user->toArray());
}
public function test_admin_scope_returns_only_admins(): void
{
User::factory()->admin()->create();
User::factory()->count(3)->create();
$this->assertCount(1, User::admin()->get());
}
}
class ProductTest extends TestCase
{
use RefreshDatabase;
public function test_active_scope_filters_correctly(): void
{
Product::factory()->count(3)->create(['is_active' => true]);
Product::factory()->count(2)->create(['is_active' => false]);
$this->assertCount(3, Product::active()->get());
}
public function test_it_belongs_to_a_user(): void
{
$user = User::factory()->create();
$product = Product::factory()->create(['user_id' => $usYour agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Other skills on ecc.
- /everything-claude-code
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Open skill - /accessibility
Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA
Open skill - /agent-architecture-audit
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for
Open skill - /agent-eval
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics
Open skill - /agent-harness-construction
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates.
Open skill - /agent-introspection-debugging
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Open skill

