From atum-stack-backend
Guides TDD workflow for Laravel with PHPUnit/Pest: unit/feature/integration tests, factories, DB strategies (RefreshDatabase), fakes, and 80%+ coverage targets.
npx claudepluginhub arnwaldn/atum-plugins-collection --plugin atum-stack-backendThis skill uses the workspace's default tool permissions.
Test-driven development for Laravel applications using PHPUnit and Pest with 80%+ coverage (unit + feature).
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
Test-driven development for Laravel applications using PHPUnit and Pest with 80%+ coverage (unit + feature).
Choose layers based on scope:
RefreshDatabase for most feature/integration tests (runs migrations once per test run, then wraps each test in a transaction when supported; in-memory databases may re-migrate per test)DatabaseTransactions when the schema is already migrated and you only need per-test rollbackDatabaseMigrations when you need a full migrate/fresh for every test and can afford the costUse RefreshDatabase as the default for tests that touch the database: for databases with transaction support, it runs migrations once per test run (via a static flag) and wraps each test in a transaction; for :memory: SQLite or connections without transactions, it migrates before each test. Use DatabaseTransactions when the schema is already migrated and you only need per-test rollbacks.
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']);
}
}
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']);
}
}
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']);
});
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']);
});
$user = User::factory()->state(['role' => 'admin'])->create();
RefreshDatabase for clean stateassertDatabaseHas over manual queriesuse 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);
}
}
Bus::fake() for jobsQueue::fake() for queued workMail::fake() and Notification::fake() for notificationsEvent::fake() for domain eventsuse 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);
use Laravel\Sanctum\Sanctum;
Sanctum::actingAs($user);
$response = $this->getJson('/api/projects');
$response->assertOk();
Http::fake() to isolate external APIsHttp::assertSent()pcov or XDEBUG_MODE=coverage in CIphp artisan testvendor/bin/phpunitvendor/bin/pestphpunit.xml to set DB_CONNECTION=sqlite and DB_DATABASE=:memory: for fast testsuse Illuminate\Support\Facades\Gate;
$this->assertTrue(Gate::forUser($user)->allows('update', $project));
$this->assertFalse(Gate::forUser($otherUser)->allows('update', $project));
When using Inertia.js, assert on the component name and props with the Inertia testing helpers.
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')
);
}
}
Prefer assertInertia over raw JSON assertions to keep tests aligned with Inertia responses.