11

I'm testing a form where user must introduce some text between let's say 100 and 500 characters.

I use to emulate the user input:

$this->actingAs($user)
->visit('myweb/create')
->type($this->faker->text(1000),'description')
->press('Save')
->see('greater than');

Here I'm looking for the greater than piece of text in the response... It depends on the translation specified for that validation error.

How could do the same test without having to depend on the text of the validation error and do it depending only on the error itself?

Controller:

public function store(Request $request)
{
    $success = doStuff($request);
    if ($success){
        Flash::success('Created');
    } else {
        Flash::error('Fail');
    }

    return Redirect::back():
}

dd(Session::all()):

 `array:3 [
  "_token" => "ONoTlU2w7Ii2Npbr27dH5WSXolw6qpQncavQn72e"
  "_sf2_meta" => array:3 [
    "u" => 1453141086
    "c" => 1453141086
    "l" => "0"
  ]
  "flash" => array:2 [
    "old" => []
    "new" => []
  ]
]
javier_domenech
  • 5,995
  • 6
  • 37
  • 59

5 Answers5

11

you can do it like so -

$this->assertSessionHas('flash_notification.level', 'danger'); if you are looking for a particular error or success key.

or use $this->assertSessionHasErrors();

Ayo Akinyemi
  • 792
  • 4
  • 7
  • Does not work even if I'm truly getting the validation error. assertSessionHasErrors() validates to false. `Session missing key: errors Failed asserting that false is true. ` – javier_domenech Jan 13 '16 at 15:44
  • may be something related with laravel 5.1 and not my controller, look: https://laracasts.com/discuss/channels/testing/session-missing-key-in-phpunit – javier_domenech Jan 15 '16 at 12:41
  • @AyoAkinyemi What you are showing is not a `validation error testing`, it is `flash messages testing` – Yevgeniy Afanasyev Sep 20 '18 at 00:59
9

I think there is more clear way to get an exact error message from session.

    /** @var ViewErrorBag $errors */
    $errors = request()->session()->get('errors');
    /** @var array $messages */
    $messages = $errors->getBag('default')->getMessages();
    $emailErrorMessage = array_shift($messages['email']);

    $this->assertEquals('Already in use', $emailErrorMessage);

Pre-requirements: code was tested on Laravel Framework 5.5.14

yuklia
  • 6,733
  • 5
  • 20
  • 26
2

get the MessageBag object from from session erros and get all the validation error names using $errors->get('name')

    $errors = session('errors');
    $this->assertSessionHasErrors();
    $this->assertEquals($errors->get('name')[0],"The title field is required.");

This works for Laravel 5 +

User123456
  • 2,492
  • 3
  • 30
  • 44
0

Your test doesn't have a post call. Here is an example using Jeffery Way's flash package

Controller:

public function store(Request $request, Post $post)
{
    $post->fill($request->all());
    $post->user_id = $request->user()->id;

    $created = false;

    try {
        $created = $post->save();
    } catch (ValidationException $e) {
        flash()->error($e->getErrors()->all());
    }

    if ($created) {
        flash()->success('New post has been created.');
    }

    return back();
}

Test:

public function testStoreSuccess()
{
    $data = [
        'title' => 'A dog is fit',
        'status' => 'active',
        'excerpt' => 'Farm dog',
        'content' => 'blah blah blah',
    ];

    $this->call('POST', 'post', $data);

    $this->assertTrue(Post::where($data)->exists());
    $this->assertResponseStatus(302);
    $this->assertSessionHas('flash_notification.level', 'success');
    $this->assertSessionHas('flash_notification.message', 'New post has been created.');
}
Ayo Akinyemi
  • 792
  • 4
  • 7
  • pressing the save button (`->press('Save')`) shall be sending a post request too. BTW tried yours but now assertions fail because of the token: `testing.ERROR: exception 'Illuminate\Session\TokenMismatchException' in /var/www/html/anuncios.com/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/VerifyCsrfToken.php:53` – javier_domenech Jan 18 '16 at 12:42
  • add these traits... DatabaseTransactions, WithoutMiddleware to your test class. – Ayo Akinyemi Jan 18 '16 at 14:22
  • ok, (I can also `do $this->be($some_user);`). Still, assertion fails, no flash message thrown, I updated the question with a var_dump of the session after posting. Could be because the error is passed by the FormRequest and not specifically calling Flash facade? ` – javier_domenech Jan 18 '16 at 18:22
  • hmm...interesting... is there a way we can chat about this? – Ayo Akinyemi Jan 19 '16 at 12:34
  • well if we keep commenting a chat might open automatically, idk any other way – javier_domenech Jan 19 '16 at 18:55
  • try pulling in Jeffrey's flash package and see if my test will work for you – Ayo Akinyemi Jan 19 '16 at 19:04
  • I'm already using Jeffs package (included in Laravel 5.1). At this point, I'm pretty sure there must be some issue there (again: https://laracasts.com/discuss/channels/testing/session-missing-key-in-phpunit) – javier_domenech Jan 19 '16 at 19:33
  • hahaha Ayo, guess what. I was checking how did Jeff implement Flash package (https://laracasts.com/lessons/flexible-flash-messages)... and then I realized that there was a lack of `Session::start()`, it must be explicitly declared at the setUp or the test method. Now, your test assertions work :-) – javier_domenech Jan 19 '16 at 19:52
0

try to split your tests into units, say if you testing a controller function

you may catch valication exception, like so:

} catch (ValidationException $ex) {

if it was generated manually, this is how it should be generated:

throw ValidationException::withMessages([
            'abc' => ['my message'],
        ])->status(400);

you can assert it liks so

$this->assertSame('my message', $ex->errors()['abc'][0]);

if you cannot catch it, but prefer testing routs like so:

    $response = $this->json('POST', route('user-post'), [
        'name'        => $faker->name,
        'email'       => $faker->email,            
    ]);

then you use $response to assert that the validation has happened, like so

$this->assertSame($response->errors->{'name'}[0], 'The name field is required.');

PS

in the example I used

$faker = \Faker\Factory::create();

ValidationException is used liks this

use Illuminate\Validation\ValidationException;

just remind you that you don't have to generate exceptions manually, use validate method for common cases:

$request->validate(['name' => [
            'required',
        ],
    ]);

my current laravel version is 5.7

Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191
  • > 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 https://stackoverflow.com/questions/18647836/how-do-i-test-for-an-exact-exception-message-rather-than-a-substring-with-phpu – Sandra Jun 04 '20 at 12:48
  • as a note, the exception doesn't contain the validation message (I only get `The given data was invalid`) – Sandra Jun 04 '20 at 12:56
  • 1
    exception doesn't contain the validation message - because a single exception can carry information of multiple validation failures, the exception has an array of messages `$ex->errors()` – Yevgeniy Afanasyev Jun 10 '20 at 07:40