Skip to content

Testing Filament Resources, Actions, and Form Assertions with Pest

A practical guide to writing reliable Pest tests for Filament v3 resources — covering table actions, form submissions, and infolist assertions without reaching for a browser.

Published · Updated · 4 min read

Testing Filament Resources, Actions, and Form Assertions with Pest

Why Testing Filament Deserves Its Own Strategy

Filament ships a first-class testing API built on top of Livewire's testing utilities. Most teams either skip it entirely or write brittle browser tests. Neither is acceptable when a resource manages production data. The goal here is fast, deterministic Pest tests that cover the paths users actually hit.

Project Setup

Install the Filament testing package alongside Pest:

composer require --dev filament/filament pestphp/pest-plugin-laravel

Filament's helpers live in Filament\Tests and are pulled in automatically when you use livewire() inside a test. No extra trait is needed beyond RefreshDatabase.


Testing a List Page

The list page is the most common entry point. Assert that the table renders and that records appear:

use App\Filament\Resources\OrderResource;
use App\Models\Order;

it('renders the order list page', function () {
    $this->actingAs(User::factory()->admin()->create());

    $orders = Order::factory(3)->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->assertCanSeeTableRecords($orders);
});

assertCanSeeTableRecords checks that the Livewire component's rendered HTML contains each model's row — it does not fire a real browser.

Filtering and Searching

it('filters orders by status', function () {
    $pending = Order::factory()->pending()->create();
    $shipped = Order::factory()->shipped()->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->filterTable('status', 'pending')
        ->assertCanSeeTableRecords([$pending])
        ->assertCanNotSeeTableRecords([$shipped]);
});

The filterTable($filterName, $value) helper maps directly to the filter key you registered on the table.


Testing Table Actions

Bulk actions and row actions are where bugs hide. Test them explicitly:

it('marks an order as shipped via row action', function () {
    $order = Order::factory()->pending()->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->callTableAction('mark_shipped', $order)
        ->assertHasNoTableActionErrors();

    expect($order->fresh()->status)->toBe('shipped');
});

For actions that open a modal with a form, fill the fields before calling:

it('assigns a courier via action modal', function () {
    $order = Order::factory()->create();
    $courier = Courier::factory()->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->mountTableAction('assign_courier', $order)
        ->setTableActionData(['courier_id' => $courier->id])
        ->callMountedTableAction()
        ->assertHasNoTableActionErrors();

    expect($order->fresh()->courier_id)->toBe($courier->id);
});

Testing Create and Edit Forms

Form assertions validate that Filament's schema maps correctly to your model:

it('creates an order with valid data', function () {
    livewire(OrderResource\Pages\CreateOrder::class)
        ->fillForm([
            'customer_id' => Customer::factory()->create()->id,
            'notes'       => 'Rush delivery',
            'status'      => 'pending',
        ])
        ->call('create')
        ->assertHasNoFormErrors();

    $this->assertDatabaseHas('orders', ['notes' => 'Rush delivery']);
});

it('fails validation when customer is missing', function () {
    livewire(OrderResource\Pages\CreateOrder::class)
        ->fillForm(['notes' => 'No customer'])
        ->call('create')
        ->assertHasFormErrors(['customer_id' => 'required']);
});

Edit Page — Pre-filled State

it('pre-fills the edit form with existing data', function () {
    $order = Order::factory()->create(['notes' => 'Original note']);

    livewire(OrderResource\Pages\EditOrder::class, ['record' => $order->getRouteKey()])
        ->assertFormSet(['notes' => 'Original note']);
});

assertFormSet is the quickest way to verify that fill() inside mount() works correctly.


Authorization Inside Tests

Don't forget to test that unauthorized users are blocked:

it('prevents non-admins from deleting orders', function () {
    $this->actingAs(User::factory()->create()); // no admin role
    $order = Order::factory()->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->assertTableActionHidden('delete', $order);
});

assertTableActionHidden confirms the action is not rendered for that record — no need to call it and catch an exception.


Key Takeaways

  • Use livewire(PageClass::class) — Filament pages are Livewire components and test identically.
  • filterTable, callTableAction, and fillForm cover 90% of real user paths.
  • Always assert the database state after mutations, not just the absence of errors.
  • Test authorization with assertTableActionHidden / assertTableActionVisible rather than role-checking in isolation.
  • Keep each test focused on one behavior; share fixture setup via Pest beforeEach or dataset factories.

Frequently asked questions

Do I need to publish Filament's test helpers or install a separate package?

No. Filament's testing helpers are bundled with the main `filament/filament` package. As long as you have Pest and the Laravel plugin installed, you can call `livewire()` directly on any Filament page class inside your tests.

How do I test a custom action that dispatches a job or fires an event?

Wrap the action call with `Bus::fake()` or `Event::fake()` before mounting the Livewire component, then call `callTableAction()` or `callMountedTableAction()` as normal. Assert the job or event was dispatched after the call completes.

Can these tests run in CI without a database?

They require a database because Filament queries Eloquent models. Use SQLite in-memory (`:memory:`) in your `phpunit.xml` for fast CI runs, combined with `RefreshDatabase` to keep each test isolated.

Mohamed Said

About the author

Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

About GitHub LinkedIn WhatsApp Email Address My CV

Have a technical challenge?

Tell me what you’re building. I reply within two working days.

Discuss your project