Testing Filament v4 with Pest: Resources &amp; Actions | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://msaied.com) - [Home](https://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [About](https://msaied.com#about)

           [  Contact](https://msaied.com#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://msaied.com)
- [ProjectsCase studies](https://msaied.com/projects)
- [ArticlesEngineering notes](https://msaied.com/articles)
- [CertificatesCredentials](https://msaied.com/certificates)
- [AboutHow I work](https://msaied.com#about)
- [ContactGet in touch](https://msaied.com#contact)

  [Start a conversation](https://msaied.com#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://msaied.com)
2. /
3. [Articles](https://msaied.com/articles)
4. /
5. Testing Filament Resources, Actions, and Form Assertions with Pest

 Testing Filament Resources, Actions, and Form Assertions with Pest
===================================================================

 A practical guide to writing expressive, maintainable Pest tests for Filament v4 resources — covering list tables, create/edit forms, custom actions, and authorization assertions without brittle DOM selectors.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://msaied.com#person) Published 18 Oct 2026 · Updated 18 Oct 2026 · 3 min read

ShareCopy linkCopied

 ![Testing Filament Resources, Actions, and Form Assertions with Pest](https://cdn.msaied.com/793/9ec9d2cf138593ecc4292fa9fa49ee7a.png) 

  On this page +1. [Why Testing Filament Feels Hard (and How to Fix It)](#why-testing-filament-feels-hard-and-how-to-fix-it)
2. [Project Setup](#project-setup)
3. [Testing the List Table](#testing-the-list-table)
4. [Testing Create and Edit Forms](#testing-create-and-edit-forms)
5. [Testing Table Actions](#testing-table-actions)
6. [Testing Authorization](#testing-authorization)
7. [Key Takeaways](#key-takeaways)

 Why Testing Filament Feels Hard (and How to Fix It)
---------------------------------------------------

Filament panels are Livewire components under the hood. That means you can drive them entirely through `Livewire::test()` and Pest's fluent assertions — no browser, no Dusk, no fragile CSS selectors. The trick is knowing *which* component class to target and *which* Livewire properties map to form fields.

### Project Setup

Install the first-party test helpers:

```bash
composer require --dev filament/filament

```

Filament ships test helpers via `Filament\Testing` traits. In your `TestCase` you only need:

```php
use Filament\Facades\Filament;

beforeEach(function () {
    Filament::setCurrentPanel(Filament::getPanel('admin'));
});

```

This ensures policies, navigation, and panel-scoped auth all resolve correctly.

---

### Testing the List Table

```php
use App\Filament\Resources\OrderResource\Pages\ListOrders;
use App\Models\Order;

it('lists orders for the authenticated admin', function () {
    $admin = User::factory()->admin()->create();
    $orders = Order::factory(3)->create();

    livewire(ListOrders::class)
        ->actingAs($admin)
        ->assertCanSeeTableRecords($orders);
});

it('hides cancelled orders by default', function () {
    $cancelled = Order::factory()->cancelled()->create();

    livewire(ListOrders::class)
        ->actingAs(adminUser())
        ->assertCanNotSeeTableRecords([$cancelled]);
});

```

`assertCanSeeTableRecords` checks that the Eloquent records appear in the rendered table — it compares primary keys, not HTML.

---

### Testing Create and Edit Forms

```php
use App\Filament\Resources\OrderResource\Pages\CreateOrder;
use App\Filament\Resources\OrderResource\Pages\EditOrder;

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

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

it('validates required fields on create', function () {
    livewire(CreateOrder::class)
        ->actingAs(adminUser())
        ->fillForm(['customer_id' => null])
        ->call('create')
        ->assertHasFormErrors(['customer_id' => 'required']);
});

```

For edit pages, pass the record via `mountUsing`:

```php
it('saves edited order status', function () {
    $order = Order::factory()->create(['status' => 'pending']);

    livewire(EditOrder::class, ['record' => $order->getRouteKey()])
        ->actingAs(adminUser())
        ->fillForm(['status' => 'shipped'])
        ->call('save')
        ->assertHasNoFormErrors();

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

```

---

### Testing Table Actions

Custom and built-in table actions are dispatched via `callTableAction`:

```php
it('marks an order as refunded via table action', function () {
    $order = Order::factory()->create(['status' => 'shipped']);

    livewire(ListOrders::class)
        ->actingAs(adminUser())
        ->callTableAction('refund', $order)
        ->assertNotified('Order refunded');

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

```

For actions that open a modal with a form:

```php
livewire(ListOrders::class)
    ->actingAs(adminUser())
    ->mountTableAction('adjust_total', $order)
    ->setTableActionData(['total' => 49.99])
    ->callMountedTableAction()
    ->assertHasNoTableActionErrors();

```

---

### Testing Authorization

Filament respects policies automatically. Test the *absence* of actions for unauthorized users:

```php
it('hides delete action from non-admin users', function () {
    $viewer = User::factory()->viewer()->create();
    $order = Order::factory()->create();

    livewire(ListOrders::class)
        ->actingAs($viewer)
        ->assertTableActionHidden('delete', $order);
});

it('shows delete action to admins', function () {
    $order = Order::factory()->create();

    livewire(ListOrders::class)
        ->actingAs(adminUser())
        ->assertTableActionVisible('delete', $order);
});

```

---

### Key Takeaways

- Use `livewire(PageClass::class)` — target the **page** component, not the resource class itself.
- `fillForm` / `assertHasNoFormErrors` / `assertHasFormErrors` cover 90% of form testing needs.
- `callTableAction` and `callMountedTableAction` handle both simple and modal-based actions.
- `assertCanSeeTableRecords` compares model keys — immune to markup changes.
- Always set the active Filament panel in `beforeEach` to avoid policy and auth resolver mismatches.
- Authorization tests are cheap: one positive and one negative case per action is usually sufficient.

- [filament](https://msaied.com/articles?search=filament)
- [pest](https://msaied.com/articles?search=pest)
- [testing](https://msaied.com/articles?search=testing)
- [laravel](https://msaied.com/articles?search=laravel)

 Frequently asked questions 
---------------------------

  Do I need Laravel Dusk to test Filament resources?No. Filament pages are Livewire components, so Pest's Livewire testing utilities cover form submissions, table actions, and authorization checks entirely in-process — no browser required.

   How do I test a custom Filament page that isn't a standard Create/Edit/List page?Use `livewire(YourCustomPage::class)` directly. Mount any required route parameters via the second argument array, e.g. `livewire(ManageOrderItems::class, \['record' =&gt; $order-&gt;id\])`, then call actions and assert state as normal.

   Why does my policy not apply during tests?The most common cause is a missing `Filament::setCurrentPanel()` call in `beforeEach`. Without an active panel, Filament's auth resolver may fall back to defaults that bypass your registered policies.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://msaied.com#person)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](https://msaied.com#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleDomain-Driven Design in Laravel: Actions, DTOs, and Value Objects Without Bloat](https://msaied.com/articles/domain-driven-design-in-laravel-actions-dtos-and-value-objects-without-bloat-4)  

   On this page
-------------

1. [Why Testing Filament Feels Hard (and How to Fix It)](#why-testing-filament-feels-hard-and-how-to-fix-it)
2. [Project Setup](#project-setup)
3. [Testing the List Table](#testing-the-list-table)
4. [Testing Create and Edit Forms](#testing-create-and-edit-forms)
5. [Testing Table Actions](#testing-table-actions)
6. [Testing Authorization](#testing-authorization)
7. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

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

[Start a conversation](https://msaied.com#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/792/bb8ca53bdeeb8f56f86b6a34e8e5a0f2.png)  · 4 min read### Domain-Driven Design in Laravel: Actions, DTOs, and Value Objects Without Bloat

18 Oct 2026 ](https://msaied.com/articles/domain-driven-design-in-laravel-actions-dtos-and-value-objects-without-bloat-4) [ ![](https://cdn.msaied.com/791/8bc85df3b88387f1d37d06a7c65b0de3.png)  · 4 min read### Filament v3 Custom Field Plugins: Building Reusable Input Components from Scratch

17 Oct 2026 ](https://msaied.com/articles/filament-v3-custom-field-plugins-building-reusable-input-components-from-scratch) [ ![](https://cdn.msaied.com/790/3b305e067cf21c59b93a26f96786fabd.png)  · 2 min read### PostgreSQL Window Functions in Laravel: Ranking, Running Totals, and Gap Detection

17 Oct 2026 ](https://msaied.com/articles/postgresql-window-functions-in-laravel-ranking-running-totals-and-gap-detection-3) 

  Have a technical challenge?
----------------------------

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

 [Discuss your project ↗](https://msaied.com#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://msaied.com)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://msaied.com/sitemap.xml)
