Skip to content

Testing Packages

Package tests should prove the package boots, registers its extension points, and owns its behavior.

Create a package test case that registers only the providers the package needs.

protected function getPackageProviders($app): array
{
return [
\Capell\Core\Providers\CapellServiceProvider::class,
\Capell\Admin\Providers\AdminServiceProvider::class,
\Capell\Example\Providers\ExampleServiceProvider::class,
\Capell\Example\Providers\AdminServiceProvider::class,
];
}

Force packages installed in tests when testing installed-only behavior:

CapellCore::forcePackageInstalled('capell-app/example');

Every package with capell.json should test that required manifest fields exist and providers are loadable.

Provider tests should assert:

  • package metadata is registered.
  • admin pages are registered through CapellAdmin.
  • dashboard widgets are registered in the correct dashboard slot.
  • settings schemas are registered when present.
it('registers package metadata', function (): void {
$package = CapellCore::getPackage('capell-app/example');
expect($package->name)->toBe('capell-app/example');
});

Test Actions directly. Avoid HTTP tests unless the route or Filament page behavior is the subject.

it('builds the package output data', function (): void {
$data = BuildExampleOutputAction::run($input);
expect($data)->toBeInstanceOf(ExampleOutputData::class);
});

Test direct registration first, then add one render test for the user-facing surface.

it('tags the page schema extender', function (): void {
$extenders = collect(app()->tagged(PageSchemaExtender::TAG));
expect($extenders)
->toContain(fn (PageSchemaExtender $extender): bool => $extender instanceof ExamplePageSchemaExtender);
});
it('registers an extension settings page', function (): void {
CapellCore::forcePackageInstalled('capell-app/example');
expect(resolve(ExtensionPageRegistry::class)->get('capell-app/example'))
->toBe(ExampleSettingsPage::class);
});

Any package that renders public HTML needs presence and absence assertions.

it('renders public package output without authoring state', function (): void {
$response = $this->get('/example-page');
$response->assertOk();
expect($response->getContent())
->toContain('Expected public copy')
->not->toContain('data-capell-editor')
->not->toContain('field_path')
->not->toContain('signed');
});

Marketplace-adjacent packages should prove local compatibility and authorization state handling without treating remote metadata as trusted code.

it('records install intent only when an instance is connected', function (): void {
MarketplaceInstance::factory()->create();
$acquisition = CreateExtensionAcquisitionAction::run($listing);
expect($acquisition->composerCommand)->toContain('composer require');
});

Use arch tests to prevent package boundary regressions:

  • package code should not import app-specific classes.
  • frontend providers should not import Filament.
  • admin providers should not run on frontend-only contexts.
  • packages should not import sibling packages unless Composer requires them.

Run the package suite during implementation:

Terminal window
vendor/bin/pest packages/example/tests --configuration=phpunit.xml

Run the host suite only when the package touches shared contracts, public rendering, install/upgrade, or admin boot:

Terminal window
composer test