Filament testen met Pest: resources, forms en actions
Terug naar blog

Filament testen met Pest: resources, forms en actions

8 min leestijd

Filament testen met Pest kost je per resource zo'n 5 tot 10 korte tests: lijst, aanmaken, validatie, bewerken, acties en rechten. Elke Filament-pagina is een Livewire-component, dus je test hem met livewire() en Filament's eigen helpers als fillForm(), assertHasFormErrors() en callAction(). Geen...

Filament testen met Pest kost je per resource zo'n 5 tot 10 korte tests: lijst, aanmaken, validatie, bewerken, acties en rechten. Elke Filament-pagina is een Livewire-component, dus je test hem met livewire() en Filament's eigen helpers als fillForm(), assertHasFormErrors() en callAction(). Geen browser nodig, en een suite van honderden tests draait in CI in minuten.

Waarom zou je een Filament admin panel testen?

Omdat een backoffice geen bijzaak is. Daar worden orders aangepast, prijzen gewijzigd en klanten verwijderd. Een kapotte validatieregel of een delete-knop die ineens zichtbaar is voor een stagiair, merk je pas als het misgaat.

Wat niemand je vertelt: Filament genereert zoveel voor je dat het voelt alsof er niets te testen valt. Dat klopt niet. Jij schrijft de validatie, de visible()-closures, de policies, de custom actions en de query-scopes. Daar zitten de bugs.

Onze eigen backoffice draait op Filament: blog, leads, afspraken, cases en een database met Magento-shops. Bij een upgrade van Filament is een testsuite het enige dat je objectief vertelt of alles nog werkt. Zonder tests is een upgrade van Filament 3 naar 5 gokken.

De tests die het meeste opleveren, op volgorde:

  1. Rechten. Wie mag welke resource en welke actie zien?
  2. Validatie. Worden foute invoer en verplichte velden geweigerd?
  3. Custom actions. Doet "Factuur versturen" echt wat het moet doen?
  4. Filters en scopes. Ziet een gebruiker alleen zijn eigen records?
  5. Happy path. Kan een record aangemaakt en bewerkt worden?

Hoe zet je Pest op voor Filament?

Je hebt Pest plus de Livewire-plugin van Pest nodig. Daarna test je elke Filament-pagina met de livewire()-functie, ingelogd als een gebruiker die toegang heeft tot het panel.

Installatie van de plugin, volgens de Pest-documentatie:

bash
composer require pestphp/pest-plugin-livewire --dev

Gebruik je PHPUnit in plaats van Pest? Dan werkt alles hetzelfde. Filament's documentatie zegt het letterlijk: vervang livewire() door Livewire::test().

Een basisopzet in tests/Pest.php of in een beforeEach:

php
use App\Models\User;

beforeEach(function () {
    $this->actingAs(User::factory()->create());
});

Twee dingen die in de praktijk vaak misgaan:

  • Meerdere panels. Test je een panel dat niet de default is (bijvoorbeeld een klantportaal naast je admin)? Vertel Filament dan welk panel actief is met Filament::setCurrentPanel(Filament::getPanel('portal')) in je setup.
  • canAccessPanel(). Implementeert je User-model FilamentUser? Dan moet je factory-gebruiker ook echt toegang hebben. Anders falen al je tests met een 403 en zoek je je suf.

Hoe test je een Filament resource tabel?

Je mount de List-pagina van je resource en gebruikt de table-helpers: assertCanSeeTableRecords(), searchTable(), sortTable() en filterTable(). Elke helper is chainable.

Een resource in Filament v4 en v5 staat standaard in een eigen map, zoals App\Filament\Resources\Orders\Pages\ListOrders. Een basistest:

php
use App\Filament\Resources\Orders\Pages\ListOrders;
use App\Models\Order;
use function Pest\Livewire\livewire;

it('toont orders in de tabel', function () {
    $orders = Order::factory()->count(5)->create();

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

Zoeken, sorteren en filteren test je zo:

php
it('filtert op betaalde orders', function () {
    $betaald = Order::factory()->paid()->count(3)->create();
    $open = Order::factory()->unpaid()->count(2)->create();

    livewire(ListOrders::class)
        ->filterTable('is_paid')
        ->assertCanSeeTableRecords($betaald)
        ->assertCanNotSeeTableRecords($open)
        ->assertCountTableRecords(3);
});

Handige helpers voor tabellen:

HelperWat het test
assertCanSeeTableRecords($records)Records staan in de tabel
assertCanNotSeeTableRecords($records)Records staan er níet in (scopes, soft deletes)
assertCountTableRecords(4)Exact aantal records
searchTable('term')Globale zoekfunctie
sortTable('title', 'desc')Sortering op een kolom
filterTable('author_id', $id)Filter met waarde
resetTableFilters()Filters terugzetten
assertTableColumnExists('author')Kolom bestaat
loadTable()Nodig als je tabel deferLoading() gebruikt

Die laatste is een klassieke valkuil. Gebruik je deferLoading() voor grote datasets, dan is de tabel leeg tot je loadTable() aanroept. Je test faalt dan zonder duidelijke reden.

Hoe test je Filament forms met fillForm en assertHasFormErrors?

Je vult het formulier met fillForm(), roept de submit-methode aan met call('create') of call('save'), en controleert het resultaat met assertHasNoFormErrors() of assertHasFormErrors(). Dat werkt in v4 en v5 identiek.

Een create-test met database-check:

php
use App\Filament\Resources\Products\Pages\CreateProduct;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Livewire\livewire;

it('maakt een product aan', function () {
    livewire(CreateProduct::class)
        ->fillForm([
            'name' => 'Werkhandschoen L',
            'sku' => 'WH-L-001',
            'price' => 12.95,
        ])
        ->call('create')
        ->assertHasNoFormErrors()
        ->assertNotified()
        ->assertRedirect();

    assertDatabaseHas('products', ['sku' => 'WH-L-001']);
});

Validatie test je met de naam van de regel per veld:

php
it('weigert een product zonder naam', function () {
    livewire(CreateProduct::class)
        ->fillForm(['name' => null, 'sku' => 'WH-L-001'])
        ->call('create')
        ->assertHasFormErrors(['name' => 'required'])
        ->assertNotNotified()
        ->assertNoRedirect();
});

Met Pest-datasets test je in één keer alle regels. Eén test, tien varianten: verplicht, maximale lengte, uniek, numeriek. Dat is de rekenstap waarom Pest hier zo prettig is.

Voor edit-pagina's controleer je eerst of het formulier goed gevuld is:

php
livewire(EditProduct::class, ['record' => $product->getRouteKey()])
    ->assertOk()
    ->assertSchemaStateSet([
        'name' => $product->name,
        'sku' => $product->sku,
    ]);

Wat is er veranderd ten opzichte van Filament 3?

Sinds v4 zijn forms en infolists samengevoegd in Schemas. Daarom heet de assertion nu assertSchemaStateSet(). Tutorials uit het v3-tijdperk gebruiken assertFormSet() en callTableAction(); volg in v4 en v5 de huidige documentatie. Zie ook wat er nieuw is in Filament 4 en 5.

Andere form-helpers die je nodig gaat hebben:

  • assertFormFieldVisible('btw_nummer') en assertFormFieldHidden() voor conditionele velden.
  • assertFormFieldDisabled() voor velden die na publicatie op slot gaan.
  • Repeater::fake() als je repeaters test. Die vervangt de UUID-keys door voorspelbare nummers. Roep de teruggegeven closure aan om het weer ongedaan te maken.
  • goToNextWizardStep() en assertWizardCurrentStep(2) voor wizards.
  • Meerdere forms op één pagina? Geef de form-naam mee als tweede argument aan fillForm() en assertHasFormErrors().

Hoe test je Filament actions?

Met callAction(). Voor pagina-acties geef je de naam mee. Voor tabel-, bulk- en schema-acties gebruik je sinds v4 een TestAction-object uit Filament\Actions\Testing\TestAction.

Een header-actie op een edit-pagina met modal-formulier:

php
livewire(EditInvoice::class, ['record' => $invoice->getRouteKey()])
    ->callAction('send', data: ['email' => '[email protected]'])
    ->assertHasNoFormErrors();

Een rij-actie in de tabel:

php
use Filament\Actions\Testing\TestAction;

livewire(ListInvoices::class)
    ->callAction(TestAction::make('send')->table($invoice));

Een bulk-actie:

php
livewire(ListInvoices::class)
    ->selectTableRecords($invoices->pluck('id')->toArray())
    ->callAction(TestAction::make('send')->table()->bulk());

Na de actie controleer je het effect, niet de knop. Is de status aangepast? Is de mail in de queue gezet? Gebruik Mail::fake(), Queue::fake() of Notification::fake() zoals in elke Laravel-test.

Overzicht van de belangrijkste action-helpers:

HelperGebruik
callAction('send', data: [...])Actie uitvoeren, eventueel met modal-data
mountAction('send')Modal openen zonder uit te voeren
callMountedAction()Geopende modal bevestigen
assertActionVisible() / assertActionHidden()Zichtbaarheid
assertActionEnabled() / assertActionDisabled()Klikbaar of niet
assertActionHalted('send')Actie is bewust gestopt met halt()
assertMountedActionModalSee('tekst')Inhoud van de modal

Alle assertions accepteren ook een TestAction. Dus assertActionHidden(TestAction::make('delete')->table($order)) werkt voor rij-acties.

Hoe test je rollen en rechten in Filament?

Test rechten op twee niveaus: toegang tot de resource via een HTTP-request, en zichtbaarheid van acties via Livewire. Filament leunt op Laravel policies, dus je test in feite je policies in de context waarin ze gebruikt worden.

Toegang tot een resource:

php
use App\Filament\Resources\Orders\OrderResource;

it('blokkeert gebruikers zonder rol', function () {
    $this->actingAs(User::factory()->create());

    $this->get(OrderResource::getUrl('index'))
        ->assertForbidden();
});

Acties per rol:

php
it('verbergt verwijderen voor support-medewerkers', function () {
    $this->actingAs(User::factory()->support()->create());
    $order = Order::factory()->create();

    livewire(ListOrders::class)
        ->assertActionHidden(TestAction::make('delete')->table($order));
});

Gebruik je Filament Shield of een eigen rollenstructuur? Lees dan ook rollen en rechten met Shield. De les is dezelfde: test elke rol tegen elke gevoelige actie. Een Pest-dataset met rollen maakt daar een matrix van in een paar regels.

Multi-tenant? Test dan expliciet dat tenant A de records van tenant B niet ziet met assertCanNotSeeTableRecords(). Dat is de test met de hoogste waarde in je hele suite.

Hoe zet je Filament tests op in CI?

Draai Pest in GitHub Actions, GitLab CI of Bitbucket Pipelines op elke pull request. Voor de meeste Filament-apps volstaat SQLite in-memory; gebruik MySQL of PostgreSQL als je database-specifieke queries hebt.

Een minimale GitHub Actions-job bevat:

  • shivammathur/setup-php met PHP 8.2 of hoger (vereiste voor Filament v5).
  • composer install --no-interaction --prefer-dist.
  • .env.testing met DB_CONNECTION=sqlite en DB_DATABASE=:memory:.
  • vendor/bin/pest --parallel om de suite over meerdere processen te verdelen.

Praktische tips uit ons eigen werk:

  • Geen npm run build nodig voor Livewire-tests. HTTP-tests die een pagina met een custom Vite-theme renderen, kunnen wel struikelen over een ontbrekende manifest. Roep dan $this->withoutVite() aan in je setup.
  • Gebruik RefreshDatabase en snelle factories. Trage factories zijn de grootste vertrager van een Filament-suite.
  • Draai tests vóór de deploy, niet erna. Koppel je pipeline aan je deploy-flow, zoals we beschrijven in onze CI/CD-pipeline met GitHub Actions. Dat artikel gaat over Magento, de opbouw is hetzelfde.
  • Zet een upgrade-branch op met composer update filament/* en laat CI het werk doen. Groen? Dan is de upgrade een kwestie van mergen.

Wat test je niet in Filament?

Niet alles. Filament zelf heeft een eigen testsuite. Je hoeft niet te bewijzen dat een TextInput tekst accepteert.

De eerlijke versie: test wat jij schrijft, niet wat Filament levert.

Wel testenNiet testen
Eigen validatieregelsDat een standaard veld rendert
visible() / hidden()-logicaStyling en layout
Policies en rollenStandaard paginatie
Custom actions en hun side effectsStandaard delete zonder extra logica
Query-scopes en tenant-isolatieFilament's eigen componenten
Berekende velden (afterStateUpdated)Iconen, kleuren, labels

Browser-tests met Pest's browser testing of Dusk zijn zinvol voor één of twee kritieke flows. Voor de rest zijn Livewire-tests sneller en stabieler.

Samengevat

  • Elke Filament-pagina is een Livewire-component: test hem met livewire().
  • Tabellen: assertCanSeeTableRecords(), filterTable(), searchTable(), en loadTable() bij deferred loading.
  • Forms: fillForm() + call('create') + assertHasFormErrors(). Edit-state met assertSchemaStateSet().
  • Actions: callAction(), en TestAction::make()->table() voor rij- en bulk-acties.
  • Rechten: test elke rol tegen elke gevoelige actie, plus tenant-isolatie.
  • CI: SQLite in-memory, pest --parallel, op elke pull request.
  • Test je eigen logica, niet Filament zelf.

Meer over hoe wij Filament inzetten voor maatwerk-backoffices lees je op onze pagina over Laravel en Filament. Nieuw met resources? Begin bij resources, forms en tables uitgelegd.

Veelgestelde vragen

Werkt Filament testen ook met PHPUnit in plaats van Pest?

Ja. Alle Filament-helpers zijn Livewire-testmethodes. Vervang livewire(Component::class) door Livewire::test(Component::class) en de rest blijft gelijk. Pest is vooral prettiger door datasets en kortere syntax.

Wat is het verschil tussen assertFormSet en assertSchemaStateSet?

assertFormSet() komt uit Filament 3. Sinds v4 zijn forms en infolists samengevoegd in Schemas en gebruikt de documentatie assertSchemaStateSet(). In v5 is de API gelijk aan v4, dus daar gebruik je ook assertSchemaStateSet().

Hoe test ik een tabel-actie in Filament 4 of 5?

Gebruik callAction(TestAction::make('naam')->table($record)). Voor bulk-acties selecteer je eerst records met selectTableRecords() en gebruik je ->table()->bulk(). De oude callTableAction()-aanpak hoort bij v3.

Waarom is mijn Filament-tabel leeg in de test?

Meestal door deferLoading() op de tabel. Roep dan loadTable() aan vóór je assertions. Andere oorzaken: de testgebruiker heeft geen toegang tot het panel, of een tenant-scope filtert de records weg.

Hoeveel tests heeft een Filament resource nodig?

Reken op 5 tot 10 per resource: lijst, zoeken of filteren, aanmaken, validatie, bewerken, rechten en elke custom action. Simpele CRUD-resources zonder eigen logica kunnen met minder. Resources met financiële acties verdienen meer.

Moet ik Filament-tests draaien bij elke deploy?

Ja, en vóór de deploy. Een suite met Livewire-tests en SQLite in-memory is snel genoeg om op elke pull request te draaien. Zeker bij Filament-upgrades is die suite je vangnet.

Een Filament-backoffice laten bouwen of een bestaande suite op orde brengen? Neem contact op, dan kijken we er samen naar.

Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check