skip to content

In a Livewire component test, how do assertHasErrors() and assertDispatched() check validation failures and events, and what do their arguments match?

level: middleimportance: must knowfreq 45%

answer

  1. no arguments: any error at all
  2. field => rule name or message
  3. rule parameters after the colon ignored
  4. named params compared as a subset
  5. closure receives name and params

basics

~10 s

assertHasErrors('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 s

After 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

for a junior

Know the basic forms: assertHasErrors('email') after a failing call, and assertDispatched('subscribed') after a successful one.

for a middle

Explain rule-or-message matching, the colon cut, subset parameter matching, and the negative and only-these variants.

for a senior

Write assertions that fail for the right reason: pin thresholds through messages, use strict parameter values, and check events on the correct request.

for a principal

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.