Why Filament Testing Deserves Its Own Strategy
Filament panels are Livewire components under the hood. That means standard HTTP tests miss most of what matters — form validation, action modals, bulk operations, and authorization checks all live inside component state. Pest's Livewire integration, combined with Filament's own test helpers, gives you a clean path to full coverage without brittle browser tests.
Setting the Auth Context
Every Filament resource test needs an authenticated panel user. Use actingAs before mounting the component, and make sure the user satisfies your panel's auth()->check() guard.
use App\Models\User;
use App\Filament\Resources\OrderResource\Pages\ListOrders;
it('renders the orders list for an admin', function () {
$admin = User::factory()->admin()->create();
livewire(ListOrders::class)
->actingAs($admin)
->assertSuccessful();
});
If your panel uses a custom guard, pass it explicitly: actingAs($admin, 'admin').
Asserting Table Columns and Records
Filament's InteractsWithTable trait exposes helpers that map directly to what the user sees.
it('shows pending orders in the table', function () {
$admin = User::factory()->admin()->create();
Order::factory()->pending()->count(3)->create();
Order::factory()->completed()->count(2)->create();
livewire(ListOrders::class)
->actingAs($admin)
->assertCountTableRecords(3); // default filter: pending
});
Use ->assertCanSeeTableRecords($records) when you need to assert specific model instances appear, and ->assertCanNotSeeTableRecords($records) for scoping and authorization checks.
Testing Table Actions
Row actions and bulk actions are where most bugs hide. Filament provides callTableAction and callTableBulkAction.
it('marks an order as shipped via row action', function () {
$admin = User::factory()->admin()->create();
$order = Order::factory()->pending()->create();
livewire(ListOrders::class)
->actingAs($admin)
->callTableAction('mark_shipped', $order)
->assertHasNoTableActionErrors();
expect($order->fresh()->status)->toBe(OrderStatus::Shipped);
});
For actions that open a modal with a form, pass data as the third argument:
->callTableAction('reassign', $order, data: [
'assignee_id' => $agent->id,
])
->assertHasNoTableActionErrors();
Form Submission on Create/Edit Pages
Mount the CreateOrder or EditOrder page directly and use fillForm + call('create') or call('save').
use App\Filament\Resources\OrderResource\Pages\CreateOrder;
it('validates required fields on create', function () {
$admin = User::factory()->admin()->create();
livewire(CreateOrder::class)
->actingAs($admin)
->fillForm([
'customer_id' => null,
'total' => -10,
])
->call('create')
->assertHasFormErrors([
'customer_id' => 'required',
'total' => 'min',
]);
});
it('creates an order with valid data', function () {
$admin = User::factory()->admin()->create();
$customer = Customer::factory()->create();
livewire(CreateOrder::class)
->actingAs($admin)
->fillForm([
'customer_id' => $customer->id,
'total' => 150.00,
'status' => OrderStatus::Pending->value,
])
->call('create')
->assertHasNoFormErrors();
$this->assertDatabaseHas('orders', [
'customer_id' => $customer->id,
'total' => 150.00,
]);
});
Testing Authorization Inside Resources
Filament respects policies. Test that unauthorized users are blocked at the component level, not just the route.
it('prevents non-admins from deleting orders', function () {
$viewer = User::factory()->viewer()->create();
$order = Order::factory()->create();
livewire(ListOrders::class)
->actingAs($viewer)
->assertTableActionHidden('delete', $order);
});
assertTableActionHidden checks that the action is not rendered for that record — a stronger guarantee than checking HTTP status codes.
Practical Takeaways
- Always call
actingAsbefore mounting; Filament panels enforce auth at the component level. - Use
assertCountTableRecordsfor scope/filter tests — it respects the component's default filters. callTableActionwith adataarray covers modal-based actions without browser overhead.assertHasFormErrorsaccepts field-rule pairs for precise validation assertions.- Test authorization with
assertTableActionHidden/assertTableActionVisiblerather than policy unit tests alone. - Keep each test focused on one behavior; Filament component state resets between Pest tests automatically.