laravel-tdd by affaan-m/everything-claude-code
npx skills add https://github.com/affaan-m/everything-claude-code --skill laravel-tdd使用 PHPUnit 和 Pest 为 Laravel 应用程序进行测试驱动开发,实现 80% 以上的覆盖率(单元测试 + 功能测试)。
根据范围选择测试层级:
RefreshDatabase 用于大多数功能/集成测试(每次测试运行运行一次迁移,然后在支持的情况下将每个测试包装在事务中;内存数据库可能为每个测试重新迁移)DatabaseTransactions广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
DatabaseMigrations对于涉及数据库的测试,默认使用 RefreshDatabase:对于支持事务的数据库,它每次测试运行运行一次迁移(通过静态标志),并将每个测试包装在事务中;对于 :memory: SQLite 或不支持事务的连接,它在每个测试之前进行迁移。当模式已迁移且仅需要每个测试回滚时,使用 DatabaseTransactions。
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 保持状态干净assertDatabaseHas 而非手动查询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);
}
}
Bus::fake() 用于任务Queue::fake() 用于队列工作Mail::fake() 和 Notification::fake() 用于通知Event::fake() 用于领域事件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);
use Laravel\Sanctum\Sanctum;
Sanctum::actingAs($user);
$response = $this->getJson('/api/projects');
$response->assertOk();
Http::fake() 来隔离外部 APIHttp::assertSent() 断言出站负载pcov 或 XDEBUG_MODE=coveragephp artisan testvendor/bin/phpunitvendor/bin/pestphpunit.xml 设置 DB_CONNECTION=sqlite 和 DB_DATABASE=:memory: 以进行快速测试use Illuminate\Support\Facades\Gate;
$this->assertTrue(Gate::forUser($user)->allows('update', $project));
$this->assertFalse(Gate::forUser($otherUser)->allows('update', $project));
当使用 Inertia.js 时,使用 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')
);
}
}
优先使用 assertInertia 而非原始 JSON 断言,以保持测试与 Inertia 响应的一致性。
每周安装量
296
代码仓库
GitHub 星标数
102.1K
首次出现
8 天前
安全审计
安装于
codex282
cursor249
gemini-cli246
github-copilot246
amp246
cline246
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']);
});
Use factories for test data
Define states for edge cases (archived, admin, trial)
$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 jobs
Queue::fake() for queued work
Mail::fake() and Notification::fake() for notifications
Event::fake() for domain events
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);
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.
Weekly Installs
296
Repository
GitHub Stars
102.1K
First Seen
8 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex282
cursor249
gemini-cli246
github-copilot246
amp246
cline246
Spring Boot工程师技能指南:微服务架构、安全加固与云原生开发实战
2,800 周安装