Testing
Tests are PHPUnit classes, about 500 files, nearly all feature tests on a real database.
MariaDB, not SQLite
Section titled “MariaDB, not SQLite”phpunit.xml sets DB_CONNECTION=mariadb and DB_DATABASE=testing. The schema uses features SQLite
doesn’t support, and matching production avoids migration surprises. Don’t switch the test config to
SQLite to make a test pass.
The testing database must exist, with privileges for the db user:
ddev exec mysql -e "CREATE DATABASE IF NOT EXISTS testing;"ddev exec mysql -e "GRANT ALL PRIVILEGES ON testing.* TO 'db'@'%';"phpunit.xml also sets the queue to sync, cache and session to array, mail to array, and
Scout’s driver to null with a phpunit_ prefix and a dead Meilisearch host, so a test can never
touch your dev index. A test that wants to see search work turns the driver on and fakes the queue:
config(['scout.driver' => 'meilisearch']);Queue::fake();Running tests
Section titled “Running tests”| Task | Command |
|---|---|
| Everything | ddev artisan test |
| One suite | ddev artisan test --testsuite=Modules |
| One file | ddev artisan test app-modules/sales/tests/Feature/CustomerTimelineTest.php |
| By name | ddev artisan test --filter=ValidDomainTest |
There are two suites:
| Suite | Directory |
|---|---|
Unit |
tests/Unit, which holds app-level tests such as AuthorizationMatrixTest, ModuleDependencyTest and the seeder and import tests |
Modules |
app-modules/*/tests, files ending in Test.php |
A module test file that doesn’t end in Test.php is silently not run. The full run takes several
minutes, so use a filter while you work and run everything at checkpoints.
Two runs at the same time
Section titled “Two runs at the same time”RefreshDatabase resets the database under any other run using it. phpunit.xml’s values are not
forced, so the process environment wins. Give the second run its own database (create and grant it as
above):
ddev exec "DB_DATABASE=testing_panel php artisan test app-modules/customer-panel"What --env=testing does not do
Section titled “What --env=testing does not do”Conventions
Section titled “Conventions”- Extend
Tests\TestCaseand useRefreshDatabase. Name teststest_a_sentence_saying_what_happens. - No test reaches the network.
Tests\TestCase::setUp()callsHttp::preventStrayRequests(). Fake what you need withHttp::fake(). - Use the factories in each module’s
database/factories. - Fake what the test isn’t about:
Queue::fake(),Mail::fake(),Storage::fake('local'),Carbon::setTestNow(...). - Module tests may depend on modules that their
src/doesn’t:ModuleDependencyTestonly scanssrc/. - Shared fixtures live beside the tests, in
tests/Fixturesandtests/Concerns.
Filament and Livewire tests
Section titled “Filament and Livewire tests”The pattern used across the modules. Set the panel, sync the roles, sign in as a real role, then drive
the page with Livewire::test:
protected function setUp(): void{ parent::setUp();
Filament::setCurrentPanel('admin'); app(RoleDefinitions::class)->sync();
$manager = User::factory()->create(); $manager->assignRole('manager'); $this->actingAs($manager);}
public function test_a_rule_is_built(): void{ Livewire::test(CreateAutomationRule::class) ->fillForm(['name' => 'Paint red gadgets blue', /* ... */]) ->call('create') ->assertHasNoFormErrors();}Tests\TestCase has shortcuts for the signing in: actingAsRole('manager') and
actingAsSuperAdmin() sync the roles for you. Test as the role that does the job, not as a super
admin, unless the test is about the resource’s behaviour and not about access: a super admin passes
every check.
Other patterns you will see:
- Table and record actions:
->callAction(TestAction::make('promote')->table($record), [...]), withFilament\Actions\Testing\TestAction. - Edit pages:
Livewire::test(EditAutomationRule::class, ['record' => $rule->id]), then->call('save'). - Relation managers: pass
ownerRecordandpageClass, as inAutomationRuleResourceTest. - The customer panel:
Filament::setCurrentPanel('customer')and$this->actingAs($customer, 'customer'). - Who can open what:
tests/Unit/AuthorizationMatrixTest.phpasserts each role against each screen through$resource::canViewAny(). When a role’s capabilities change on purpose, update its matrix.
Storefront tests
Section titled “Storefront tests”A storefront request needs a site whose domain matches the host, with content rules and a projected
document. From app-modules/catalog/tests/Feature/ProductPageTest.php:
$site = Site::factory()->create(['domain' => 'shop.test']);SiteContentRule::create([ 'site_id' => $site->id, 'mode' => ContentRuleMode::Include, 'target_type' => Product::contentTypeHandle(), 'target_id' => null, 'include_descendants' => true, 'priority' => 0,]);$document = app(SiteDocumentIndexer::class)->index($site, $product);
$this->get('http://shop.test/'.$document->resolved_path)->assertOk();