19

How to test specific validation errors in php unit thrown in validation error ? with below code we could check session has errors, but not the exact error

$this->assertSessionHasErrors();
3
  • Possible duplicate of Unit Test Laravel's FormRequest Commented Mar 28, 2019 at 2:34
  • > I'm using phpunit 7.5 and Lumen 5.8 - when using a unit test for your Request or rules, you can do $this->expectException$this->expectException('Illuminate\Validation\ValidationException'); and $this->expectExceptionMessage = "The name field is required"; - see also stackoverflow.com/questions/18647836/… Commented Jun 4, 2020 at 12:47
  • Note: the exception doesn't contain the validation message! it just says The given data was invalid Commented Jun 4, 2020 at 12:59

9 Answers 9

23

assertSessionHasErrors can receive an array, as documented:

$this->assertSessionHasErrors([
    'field' => 'Field error message.'
]);
Sign up to request clarification or add additional context in comments.

3 Comments

you don't really need to put your error into session to test the validator. You can take errors from ValidationException like so $ex->errors()
Only the keys are used here. This doesn't test the error message, only that the field has a message.
I think that might have been true at one point, but as of 6.x, you can pass values to test specific error messages, in addition to the fields/keys themselves. PR'd that within Laravel docs: laravel.com/docs/6.x/http-tests#assert-session-has-errors
19

Got the answer

    $errors = session('errors');
    $this->assertSessionHasErrors();
    $this->assertEquals($errors->get('name')[0],"Your error message for validation");

$errors is MessageBag object which stored in laravel session when validation error thrown using $errors->get('name') you could see all the validation errors as an array

3 Comments

For Laravel 6.x (and probably earlier versions), check assertSessionHasErrors: laravel.com/docs/6.x/http-tests#assert-session-has-errors
you may need to start your session in the test's setUp()
$key = config('session.keys.errors'); $errors = session( $key );
6

You may use the combination of assertStatus and assertJson

...
->assertStatus(422)
->assertJson([
     'errors' => [
          'field' => [
               'Error message'  
          ]
      ]
]);

Comments

3

First I use

$this->post() 

instead of

$this->jsonPost()

Dont know why, for certain reason, the session would not come out.

Then I just use

$response->assertSessionHasErrors('field_name', 'Error Message!');

To find out what are the error message, you must dump it

$response->dumpSession();

Comments

2

You can use $response->assertSessionHasErrors('key')

https://laravel.com/docs/7.x/http-tests#assert-session-has-errors

an example for required attribute will be

$response = $this->json('POST', '/api/courses', $this->data([
    'name' => '',
    'api_token' => $this->user->api_token
]));

$response->assertSessionHasErrors('name');

You can add an extra assertion, to make sure that no entry was added to the database, in this case "assert no course was added"

$this->assertCount(0, Course::all());

For multiple required attributes you may use a loop something like the following:

collect(['name', 'description', 'amount'])->each(function ($field) {
    $response = $this->json('POST', '/api/courses', $this->data([
        $field => '',
        'api_token' => $this->user->api_token
    ]));

    $response->assertSessionHasErrors($field);
    $this->assertCount(0, Course::all());
});

Comments

1

There is also a more elegant way in my opinion:

If you throw an exception via the class GeneralException you can check in a unit test if the session has a flash_danger from throwing a exception.

Lets do a practical example: We want to test that the admin cannot activate an already activated catalogue item.

Test function

public function an_admin_cannot_activate_an_activated_catalogue()
{
    $catalogue = factory(Catalogue::class)->states('active')->create();
    $response = $this->get("/admin/questionnaire/catalogue/{$catalogue->id}/activate");
    $response->assertSessionHas(['flash_danger' => __('The catalogue item is already activated.')]);
}

Model/Repro function

If it is activated we throw an Exception which then can be checked by the test function.

public function activate(Catalogue $catalogue) : Catalogue
{
    if ($catalogue->is_active) {
        throw new GeneralException(__('The catalogue item is already activated.'));
    }

    $catalogue->is_active = 1;
    $activated = $catalogue->save();

    if($activated) {
        return $catalogue;
    }
}

Comments

1

actually you can easily throw errors from validation using dd() and session('errors')

since errors bag is stored in session you could add dd(session('errors')) in your unit tests to see which fields you are missing.

and finally you can write more proper test by adding $response->assertSessionHasErrors('field_name');

Comments

0

Laravel 7; In my case, I needed to ensure there was no error.

But below did ignore form-validation errors (at least mine).

$response->assertSessionHasNoErrors();

Hence I created a custom assert function in base TestCase class, like:

use PHPUnit\Framework\Constraint\RegularExpression;

// ...

public static function assertNoErrorReport(TestResponse $response)
{
    $error = static::getViewError($response);
    if ( ! empty($error)) {
        $this->fail('View contains error:' . PHP_EOL . $error);
    }
    $response->assertSessionHasNoErrors();
}

public function assertHasErrorRegExp(string $pattern, TestResponse $response, string $message = '')
{
    $error = static::getViewError($response);
    static::assertThat($error, new RegularExpression($pattern),
        empty($message) ? $error : $message);
}

public static function getViewError(TestResponse $response)
{
    $content = $response->getOriginalContent();
    if ( ! $content) {
        static::fail('View content missing.');
    }
    if ($content instanceof View) {
        $data = $content->gatherData();
        $error = $data['error'] ?? $data['errors'] ?? null;

        // Casts array to string.
        if (is_array($error)) {
            $error = '[' . join(', ', $error) . ']';
        }
        // Casts Error-bag to string.
        $error = '' . $error;
        if ($error === '[]') {
            return null;
        }
    } else {
        static::fail('Response is not a View.');
    }

    return $data;
}

However, my assertHasErrorRegExp(...) could be used for OP's case.

Comments

0

In Laravel 8+, you can use assertInvalid to test for specific validation errors

// Assert that these fields have validation errors
$response->assertInvalid(['name', 'email']);


// Assert that these fields give particular validation fail messages
$response->assertInvalid([
    'name' => 'The name field is required.',
    'email' => 'valid email address',
]);

https://laravel.com/docs/10.x/http-tests#validation-assertions

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.