Testing Filament Resources, Actions, and Form Assertions with Pest
#filament #pest #testing #laravel

Testing Filament Resources, Actions, and Form Assertions with Pest

3 min read Mohamed Said Mohamed Said

Why Filament Tests Are Worth the Effort

Filament panels generate a lot of implicit behaviour — authorization checks, form validation, table bulk actions, and page redirects — all wired together through Livewire. Skipping tests means every refactor is a gamble. The good news: Filament ships with first-class Pest/PHPUnit helpers that let you drive the entire panel through HTTP and Livewire assertions, no Dusk required.

Project Setup

Install the testing helpers if you haven't already:

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

Filament's test helpers live in Filament\Testing and are available on the livewire() helper once the panel is booted. Add a base TestCase that boots your panel:

// tests/TestCase.php
protected function setUp(): void
{
    parent::setUp();
    $this->actingAs(User::factory()->admin()->create());
}

Testing a Resource List Page

The simplest assertion confirms the page renders and shows records:

use App\Filament\Resources\OrderResource;
use App\Models\Order;
use function Pest\Laravel\get;

it('renders the order list page', function () {
    Order::factory()->count(3)->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->assertCanSeeTableRecords(Order::all());
});

assertCanSeeTableRecords checks that the Livewire component's table query returns those models — it does not assert rendered HTML, so it stays fast.

Asserting Table Actions

Table row actions and bulk actions each have dedicated helpers:

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

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

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

it('bulk-deletes selected orders', function () {
    $orders = Order::factory()->count(5)->create();

    livewire(OrderResource\Pages\ListOrders::class)
        ->callTableBulkAction('delete', $orders)
        ->assertHasNoTableActionErrors();

    expect(Order::count())->toBe(0);
});

callTableAction accepts the action name (the string you pass to Action::make()) and the target record. If the action opens a modal form, pass data as the third argument.

Form Field Assertions on Create/Edit Pages

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

    expect(Order::count())->toBe(1);
});

it('fails validation when total is negative', function () {
    livewire(OrderResource\Pages\CreateOrder::class)
        ->fillForm(['total' => -10])
        ->call('create')
        ->assertHasFormErrors(['total' => 'min']);
});

assertHasFormErrors accepts either a plain field name or a field => rule pair, matching Laravel's validation key format.

Testing Authorization Inside Filament

Filament respects canCreate, canEdit, and canDelete policy methods. Test them explicitly:

it('hides the create button for viewers', function () {
    $this->actingAs(User::factory()->viewer()->create());

    livewire(OrderResource\Pages\ListOrders::class)
        ->assertActionHidden('create');
});

This prevents silent authorization regressions when policies change.

Infolist Assertions (Filament v4)

Filament v4 infolists are also testable:

it('displays the order total in the infolist', function () {
    $order = Order::factory()->create(['total' => 250.00]);

    livewire(OrderResource\Pages\ViewOrder::class, ['record' => $order->id])
        ->assertInfolists()
        ->assertInfolists(['total' => '$250.00']);
});

Key Takeaways

  • Use livewire(PageClass::class) — never raw HTTP — for Filament page tests.
  • assertCanSeeTableRecords checks query results, not DOM; it's fast and reliable.
  • Pass action data as the third argument to callTableAction for modal-driven actions.
  • assertHasFormErrors(['field' => 'rule']) catches specific validation failures cleanly.
  • Always test authorization explicitly; policy regressions are silent without it.
  • Filament v4 infolist helpers follow the same pattern as form helpers.

Found this useful?

Frequently Asked Questions

3 questions
Q01 Do Filament Pest tests require Laravel Dusk or a real browser?
No. Filament's test helpers drive the Livewire components directly over HTTP, so tests run in-process and are much faster than browser-based tests.
Q02 How do I test a table action that opens a modal form?
Pass the form data as the third argument to `callTableAction('action_name', $record, ['field' => 'value'])`. Then assert with `assertHasNoTableActionErrors()` or check the database state.
Q03 Are these helpers compatible with both Filament v3 and v4?
Most helpers — `callTableAction`, `fillForm`, `assertHasFormErrors` — work in both versions. Filament v4 adds infolist-specific assertions; check the version's changelog for any renamed methods.

Continue reading

More Articles

View all