In a Livewire component test, how do assertHasErrors() and assertDispatched() check validation failures and events, and what do their arguments match?
answer
- no arguments: any error at all
- field => rule name or message
- rule parameters after the colon ignored
- named params compared as a subset
- closure receives name and params
basics
~10 sassertHasErrors('email') checks the field has an error, and assertHasErrors(['email' => 'email']) that a specific rule or message failed. assertDispatched('subscribed') checks an event name, and named arguments check only the parameters you list.
solid answer
~40 sAfter a `call()`, `assertHasErrors()` with no argument asserts the component has any validation error; `assertHasErrors('email')` asserts that field has one; `assertHasErrors(['email' => 'required'])` asserts that rule failed. The value matches a **rule name**, snake-cased and cut at the colon, so `'min:8'` checks `min`, or else the exact **error message**. `assertHasNoErrors()` and `assertOnlyHasErrors()` cover the negative and the "nothing else" cases. For events, `assertDispatched('subscribed')` checks the component dispatched that name; `assertDispatched('subscribed', email: '[email protected]')` also compares the named parameters, strictly but only those you pass; and a closure receives the event name and parameter array for anything more complex. `assertNotDispatched()` and `assertDispatchedTo()` complete the set.
go deeper
Know the basic forms: assertHasErrors('email') after a failing call, and assertDispatched('subscribed') after a successful one.
Explain rule-or-message matching, the colon cut, subset parameter matching, and the negative and only-these variants.
Write assertions that fail for the right reason: pin thresholds through messages, use strict parameter values, and check events on the correct request.
Set conventions for what component tests must assert about validation and events, so rules and event contracts cannot drift unnoticed.
## Where the assertions look A **Livewire component test** (`Livewire::test()`) records, after every `set()` or `call()`, the component's validation state and the request's **effects**, including the list of dispatched events. The assertions read those records; they do not re-run anything. ## Validation: `assertHasErrors()` When an action calls `$this->validate()`, or real-time validation runs on an update, Livewire catches the `ValidationException` and keeps the validator for the test. The assertion then works in four shapes: | Call | Passes when | |---|---| | `assertHasErrors()` | The component has at least one error | | `assertHasErrors('email')` or `(['email', 'consent'])` | Each listed field has an error | | `assertHasErrors(['email' => 'required'])` | The `required` rule failed for `email`, or `required` is one of its messages | | `assertHasErrors(['email' => fn ($rules, $messages) => ...])` | Your closure returns `true` | Rule matching deserves precision: 1. The expected value is cut at the first colon, so `'min:8'` matches the failed rule `min`; the `8` is **not** checked. 2. Failed rule names are snake-cased (`Required` becomes `required`); custom rule **classes** are compared by class name. 3. If the value is not a failed rule, it is compared with the field's **messages**, so `assertHasErrors(['email' => 'The email field must be a valid email address.'])` also works. The negatives are `assertHasNoErrors()` (none at all, or none for the listed fields) and `assertOnlyHasErrors([...])`, which fails if any unlisted field also has an error, which is useful when one bad field should not cascade. ## Events: `assertDispatched()` A component dispatches browser-level Livewire events with `$this->dispatch('subscribed', email: $this->email)`. The test sees them in the effects: - `assertDispatched('subscribed')`: an event with that name was dispatched in the **latest** request. - `assertDispatched('subscribed', email: '[email protected]')`: same name, and the named parameters you pass are present with **identical** values. Parameters you do not pass are ignored, so the check is a subset match. - `assertDispatched('subscribed', fn ($name, $params) => $params['email'] === '[email protected]')`: arbitrary logic. - `assertNotDispatched('subscribed')`: the event did not fire, for example after a validation failure. - `assertDispatchedTo(Counter::class, 'subscribed')`: the event was targeted at a named component with `->to()`. To test the **receiving** side, dispatch into another component's test with `->dispatch('subscribed', email: '...')` and assert on its state; that runs its `#[On]` listener. ## Custom rules and closures When a component validates with a **rule object**, say `new UniqueSubscriber`, the failed rule is recorded under its class name, so the assertion names the class: `assertHasErrors(['email' => UniqueSubscriber::class])`. For anything the shapes above cannot express, pass a closure; it receives the list of failed rule names and the list of messages for that field: ```php ->assertHasErrors(['email' => fn ($rules, $messages) => in_array('email', $rules) && count($messages) === 1]) ``` The closure form is also the honest way to pin a rule parameter: check the message text that includes the number. ## A sign-up example ```php Livewire::test(NewsletterSignup::class) ->set('email', 'not-an-email') ->call('subscribe') ->assertHasErrors(['email' => 'email']) ->assertNotDispatched('subscribed') ->set('email', '[email protected]') ->call('subscribe') ->assertHasNoErrors() ->assertDispatched('subscribed', email: '[email protected]'); ``` ## Pitfalls - **Latest request for events.** `assertDispatched()` reads the effects of the last `set()` or `call()` only, so an event dispatched two calls ago is gone. - **Errors persist.** The error bag travels in the snapshot, so an error from an earlier call is still there until a successful `validate()` or `resetErrorBag()` clears it; `assertHasErrors()` after an unrelated call can pass on a stale error. - **Parameter types.** The subset comparison is strict, so `id: '5'` does not match an event dispatched with `id: 5`. - **Rule parameters are not checked.** `assertHasErrors(['password' => 'min:12'])` passes when the rule is `min:8`; assert the message if the threshold matters. - **Which rules exist** and how messages are worded belong to Laravel's validation layer; the Livewire assertions only read the result.
- Why does assertHasErrors(['password' => 'min:12']) pass in a Livewire test when the component's rule is min:8?Livewire cuts the expected value at the colon and compares only the rule name, `min`, with the rules that failed. The parameter is never checked. To pin the threshold, assert the exact validation message instead, or test a value that passes one threshold and fails the other.
- How do you test that a Livewire component reacts to an event it listens for?Call `->dispatch('subscribed', email: '[email protected]')` on that component's `Livewire::test()` chain. It sends the event into the component as the browser would, which runs its `#[On('subscribed')]` listener, and then you assert on state or output as usual.
saying these in an interview costs you the question
- assertHasErrors(['email' => 'min:8']) also verifies the parameter 8.
- assertDispatched with named arguments requires every event parameter to be listed.
- assertHasErrors() with no argument checks that validation passed.
- assertDispatched checks events from every request in the test so far.
- assertDispatched compares parameter values loosely, so '5' matches 5.