> ## Documentation Index
> Fetch the complete documentation index at: https://filament-34a8cf01.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing schemas

> [!WARNING]
>
> You are currently viewing the documentation for Filament 4.x, which is a previous version of Filament.
>
> Looking for the current stable version? Visit the [5.x documentation](https://filament-34a8cf01.mintlify.site/5.x/testing/testing-schemas.md).

## Filling a form in a test

To fill a form with data, pass the data to `fillForm()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

livewire(CreatePost::class)
    ->fillForm([
        'title' => fake()->sentence(),
        // ...
    ]);
```

> If you have multiple schemas on a Livewire component, you can specify which form you want to fill using `fillForm([...], 'createPostForm')`.

## Testing form field and infolist entry state

To check that a form has data, use `assertSchemaStateSet()`:

```php theme={"theme":"gruvbox-dark-hard"}
use Illuminate\Support\Str;
use function Pest\Livewire\livewire;

it('can automatically generate a slug from the title', function () {
    $title = fake()->sentence();

    livewire(CreatePost::class)
        ->fillForm([
            'title' => $title,
        ])
        ->assertSchemaStateSet([
            'slug' => Str::slug($title),
        ]);
});
```

> If you have multiple schemas on a Livewire component, you can specify which schema you want to check using `assertSchemaStateSet([...], 'createPostForm')`.

You may also find it useful to pass a function to the `assertSchemaStateSet()` method, which allows you to access the form `$state` and perform additional assertions:

```php theme={"theme":"gruvbox-dark-hard"}
use Illuminate\Support\Str;
use function Pest\Livewire\livewire;

it('can automatically generate a slug from the title without any spaces', function () {
    $title = fake()->sentence();

    livewire(CreatePost::class)
        ->fillForm([
            'title' => $title,
        ])
        ->assertSchemaStateSet(function (array $state): array {
            expect($state['slug'])
                ->not->toContain(' ');
                
            return [
                'slug' => Str::slug($title),
            ];
        });
});
```

You can return an array from the function if you want Filament to continue to assert the schema state after the function has been run.

## Testing form validation

Use `assertHasFormErrors()` to ensure that data is properly validated in a form:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('can validate input', function () {
    livewire(CreatePost::class)
        ->fillForm([
            'title' => null,
        ])
        ->call('create')
        ->assertHasFormErrors(['title' => 'required']);
});
```

And `assertHasNoFormErrors()` to ensure there are no validation errors:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

livewire(CreatePost::class)
    ->fillForm([
        'title' => fake()->sentence(),
        // ...
    ])
    ->call('create')
    ->assertHasNoFormErrors();
```

> If you have multiple schemas on a Livewire component, you can pass the name of a specific form as the second parameter like `assertHasFormErrors(['title' => 'required'], 'createPostForm')` or `assertHasNoFormErrors([], 'createPostForm')`.

## Testing the existence of a form

To check that a Livewire component has a form, use `assertSchemaExists('form')`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('has a form', function () {
    livewire(CreatePost::class)
        ->assertSchemaExists('form');
});
```

> If you have multiple schemas on a Livewire component, you can pass the name of a specific form like `assertSchemaExists('createPostForm')`.

## Testing the existence of form fields

To ensure that a form has a given field, pass the field name to `assertFormFieldExists()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('has a title field', function () {
    livewire(CreatePost::class)
        ->assertFormFieldExists('title');
});
```

You may pass a function as an additional argument to assert that a field passes a given "truth test". This is useful for asserting that a field has a specific configuration:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('has a title field', function () {
    livewire(CreatePost::class)
        ->assertFormFieldExists('title', function (TextInput $field): bool {
            return $field->isDisabled();
        });
});
```

To assert that a form does not have a given field, pass the field name to `assertFormFieldDoesNotExist()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('does not have a conditional field', function () {
    livewire(CreatePost::class)
        ->assertFormFieldDoesNotExist('no-such-field');
});
```

> If you have multiple schemas on a Livewire component, you can specify which form you want to check for the existence of the field like `assertFormFieldExists('title', 'createPostForm')`.

## Testing the visibility of form fields

To ensure that a field is visible, pass the name to `assertFormFieldVisible()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('title is visible', function () {
    livewire(CreatePost::class)
        ->assertFormFieldVisible('title');
});
```

Or to ensure that a field is hidden you can pass the name to `assertFormFieldHidden()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('title is hidden', function () {
    livewire(CreatePost::class)
        ->assertFormFieldHidden('title');
});
```

> [!TIP]
>
> For both `assertFormFieldHidden()` and `assertFormFieldVisible()` you can pass the name of a specific form the field belongs to as the second argument like `assertFormFieldHidden('title', 'createPostForm')`.

## Testing disabled form fields

To ensure that a field is enabled, pass the name to `assertFormFieldEnabled()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('title is enabled', function () {
    livewire(CreatePost::class)
        ->assertFormFieldEnabled('title');
});
```

Or to ensure that a field is disabled you can pass the name to `assertFormFieldDisabled()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('title is disabled', function () {
    livewire(CreatePost::class)
        ->assertFormFieldDisabled('title');
});
```

> [!TIP]
>
> For both `assertFormFieldEnabled()` and `assertFormFieldDisabled()` you can pass the name of a specific form the field belongs to as the second argument like `assertFormFieldEnabled('title', 'createPostForm')`.

## Testing other schema components

If you need to check if a particular schema component exists rather than a field, you may use `assertSchemaComponentExists()`.  As components do not have names, this method uses the `key()` provided by the developer:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Schemas\Components\Section;

Section::make('Comments')
    ->key('comments-section')
    ->schema([
        //
    ])
```

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('comments section exists', function () {
    livewire(EditPost::class)
        ->assertSchemaComponentExists('comments-section');
});
```

To assert that a schema does not have a given component, pass the component key to `assertSchemaComponentDoesNotExist()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('does not have a conditional component', function () {
    livewire(CreatePost::class)
        ->assertSchemaComponentDoesNotExist('no-such-section');
});
```

To check if the component exists and passes a given truth test, you can pass a function to the `checkComponentUsing` argument of `assertSchemaComponentExists()`, returning true or false if the component passes the test or not:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Schemas\Components\Section;

use function Pest\Livewire\livewire;

test('comments section has heading', function () {
    livewire(EditPost::class)
        ->assertSchemaComponentExists(
            'comments-section',
            checkComponentUsing: function (Section $component): bool {
                return $component->getHeading() === 'Comments';
            },
        );
});
```

If you want more informative test results, you can embed an assertion within your truth test callback:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Schemas\Components\Section;
use Illuminate\Testing\Assert;

use function Pest\Livewire\livewire;

test('comments section is enabled', function () {
    livewire(EditPost::class)
        ->assertSchemaComponentExists(
            'comments-section',
            checkComponentUsing: function (Section $component): bool {
                Assert::assertTrue(
                    $component->isEnabled(),
                    'Failed asserting that comments-section is enabled.',
                );

                return true;
            },
        );
});
```

### Testing the visibility of schema components

To ensure that a schema component is visible, pass the key to `assertSchemaComponentVisible()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('comments section is visible', function () {
    livewire(EditPost::class)
        ->assertSchemaComponentVisible('comments-section');
});
```

Or to ensure that a schema component is hidden you can pass the key to `assertSchemaComponentHidden()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

test('comments section is hidden', function () {
    livewire(EditPost::class)
        ->assertSchemaComponentHidden('comments-section');
});
```

> [!TIP]
>
> For both `assertSchemaComponentHidden()` and `assertSchemaComponentVisible()` you can pass the name of a specific schema the component belongs to as the second argument like `assertSchemaComponentHidden('comments-section', 'createPostForm')`.

## Testing repeaters

Use `Repeater::fake()` to give repeater items predictable numeric keys instead of UUIDs when asserting their state. Call the returned function after the test to restore normal key generation:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Forms\Components\Repeater;
use function Pest\Livewire\livewire;

$undoRepeaterFake = Repeater::fake();

livewire(EditPost::class, ['record' => $post])
    ->assertSchemaStateSet([
        'quotes' => [
            [
                'content' => 'First quote',
            ],
            [
                'content' => 'Second quote',
            ],
        ],
        // ...
    ]);

$undoRepeaterFake();
```

You may also find it useful to test the number of items in a repeater by passing a function to the `assertSchemaStateSet()` method:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Forms\Components\Repeater;
use function Pest\Livewire\livewire;

$undoRepeaterFake = Repeater::fake();

livewire(EditPost::class, ['record' => $post])
    ->assertSchemaStateSet(function (array $state) {
        expect($state['quotes'])
            ->toHaveCount(2);
    });

$undoRepeaterFake();
```

### Testing repeater actions

Use `callAction()` with `TestAction::make()->schemaComponent()` to call a repeater action and then [assert its result](https://filament-34a8cf01.mintlify.site/4.x/testing/testing-actions.md#testing-actions-in-a-schema).

To interact with an action on a particular repeater item, you need to pass in the `item` argument with the key of that repeater item. If your repeater is reading from a relationship, you should prefix the ID (key) of the related record with `record-` to form the key of the repeater item:

```php theme={"theme":"gruvbox-dark-hard"}
use App\Models\Quote;
use Filament\Actions\Testing\TestAction;
use function Pest\Livewire\livewire;

$quote = Quote::first();

livewire(EditPost::class, ['record' => $post])
    ->callAction(TestAction::make('sendQuote')->schemaComponent('quotes')->arguments([
        'item' => "record-{$quote->getKey()}",
    ]))
    ->assertNotified('Quote sent!');
```

## Testing builders

Use `Builder::fake()` to give builder blocks predictable numeric keys instead of UUIDs when asserting their state. Call the returned function after the test to restore normal key generation:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Forms\Components\Builder;
use function Pest\Livewire\livewire;

$undoBuilderFake = Builder::fake();

livewire(EditPost::class, ['record' => $post])
    ->assertSchemaStateSet([
        'content' => [
            [
                'type' => 'heading',
                'data' => [
                    'content' => 'Hello, world!',
                    'level' => 'h1',
                ],
            ],
            [
                'type' => 'paragraph',
                'data' => [
                    'content' => 'This is a test post.',
                ],
            ],
        ],
        // ...
    ]);

$undoBuilderFake();
```

You can also test the number of blocks in a builder by passing a function to `assertSchemaStateSet()`:

```php theme={"theme":"gruvbox-dark-hard"}
use Filament\Forms\Components\Builder;
use function Pest\Livewire\livewire;

$undoBuilderFake = Builder::fake();

livewire(EditPost::class, ['record' => $post])
    ->assertSchemaStateSet(function (array $state) {
        expect($state['content'])
            ->toHaveCount(2);
    });

$undoBuilderFake();
```

## Testing wizards

To go to a wizard's next step, use `goToNextWizardStep()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('moves to next wizard step', function () {
    livewire(CreatePost::class)
        ->goToNextWizardStep()
        ->assertHasFormErrors(['title']);
});
```

You can also go to the previous step by calling `goToPreviousWizardStep()`:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('moves to next wizard step', function () {
    livewire(CreatePost::class)
        ->goToPreviousWizardStep()
        ->assertHasFormErrors(['title']);
});
```

If you want to go to a specific step, use `goToWizardStep()`, then the `assertWizardCurrentStep` method which can ensure you are on the desired step without validation errors from the previous:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('moves to the wizards second step', function () {
    livewire(CreatePost::class)
        ->goToWizardStep(2)
        ->assertWizardCurrentStep(2);
});
```

If you have multiple schemas on a single Livewire component, any of the wizard test helpers can accept a `schema` parameter:

```php theme={"theme":"gruvbox-dark-hard"}
use function Pest\Livewire\livewire;

it('moves to next wizard step only for fooForm', function () {
    livewire(CreatePost::class)
        ->goToNextWizardStep(schema: 'fooForm')
        ->assertHasFormErrors(['title'], schema: 'fooForm');
});
```

[Edit this page on GitHub](https://github.com/filamentphp/filament/edit/4.x/docs/10-testing/04-testing-schemas.md)

## Sponsored by

[Your logo here](https://github.com/sponsors/danharrin)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.