The happy path stored the invoice the action promised. A missing number should not create an invoice. The test posts the bad input and asserts that the response names the fields. It also asserts that the table did not gain a row.
Test-driven Laravel · Lesson 2
Validation errors
Video
01 Assert the fields, not the sentence
assertInvalid() checks the session errors. The wording of the message can change without the rule changing, so the test names the field.
it('requires a number and an issue date', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/invoices', [])
->assertInvalid(['number', 'issued_on']);
$this->assertDatabaseCount('invoices', 0);
});The form request article is where those rules live, including authorize() and prepareForValidation(). This test does not call the form request class. The HTTP response is the behaviour.
Form requests are doing more than you think.
One test for the empty request is enough when the rules are simple. A status that must be one of three values is a dataset, in Datasets, not three copies of this test.
02 A bad date is still a validation failure
An issue date in the wrong shape should fail the same way an empty one does. The test names issued_on, and the count stays zero. The message text stays out of the assertion.
it('rejects an issue date that is not a date', function () {
$user = User::factory()->create();
$this->actingAs($user)
->post('/invoices', [
'number' => 'INV-1042',
'issued_on' => 'next tuesday',
])
->assertInvalid(['issued_on'])
->assertValid(['number']);
$this->assertDatabaseCount('invoices', 0);
});assertValid() is the pair of assertInvalid(). The number was present and well formed, so a failure on number would mean the rule rejected a field this test was not trying to break. The session still redirects back. The invoice table does not gain a row either way.
assertSessionHasErrors() is the older name for the same session bag. assertInvalid() is the one to reach for. Both ignore the sentence. If the product copies the message onto the invoice page, assertSee() can check that sentence in a view test. It should not replace the field name here, or a copy edit fails the rule.
Recap
You can assert the fields that failed and that no invoice was stored.
assertInvalid()names fields, not the sentence.assertValid()is how you say the number was fine when the date was not.- A closed set of bad statuses is a dataset, not three copies of this test.
Check yourself · 3 questions
Question 1 of 3The message text changes and the rule does not. A test that asserts the sentence fails. What should it assert?