Summer Sale! All accounts are 50% off this week.

SlimDeluxe's avatar

How to set old() (flashed) data when testing Blade components?

I'm trying to test a Blade form component (Laravel 8.16) for the case when the form fails validation and I don't know how to set the request data, so that it can be read with the old() helper to properly initialize the control with the attempted submit data.

Note that this is a component test, so it's not making a HTTP request. However, this works:

In the test, if I set

Request::replace([
    'name' => 'John',
]);

i can access it in the component like this:

$current = App::environment('testing') ? (request('foo') ?? $default) : (old() ? old('foo') : $default);

... where $default is the initial state (e.g. "James"), and the one I am passing should be the one that is shown, because that's what the user tried to submit.

But this is just a hack and it's not proper testing. How can I improve this?

0 likes
5 replies
Tray2's avatar

Something like this should work when creating.

/**
 * @test
 */
public function when_validation_fails_old_values_are_show,()
{
    $this->signIn();
    $artist = Artist::factory()->make(['name' => '']);

    $response = $this->post('/artists', $artist->toArray());

    $response->assertSee($artist->dob);
}

And something like this in updating

/**
* @test
*/
public function old_value_is_shown_when_validation_fails()
{
    $this->signIn();
    $artist = Artist::factory()->create();
    $artist->name = '';
    $this->patch('/artists/' . $artist->id, $artist->toArray());
    $this->assertSee($artist->dob);
}

This above will not work. You need to check the session.

The old values are passed back by default by Laravel on validation errors so there's really no need to test it.

To test if the blade view contains the old value you need to use Cypress or similar front end testing tool.

SlimDeluxe's avatar

I know I could test this with a request but I'm trying to avoid that, since Laravel provides a more convenient way for testing components.

So my test code is:

    public function testRepopulate()
    {
        Request::replace([
            'foo' => [2, 3],
        ]);

        $view = $this->component(Checkbox::class, [
            'name' => 'foo',
            'options' => [
                1 => 'One',
                2 => 'Two',
                3 => 'Three',
            ],
            'default' => 1,
        ]);

        $view->assertSeeInOrder([
            'One',
            'checked',
            'Two',
            'checked',
            'Three',
        ]);
    }

And the test passes with the environment hack I posted before - option 2 and 3 are checked.

However I want to pass the data so that it can be read with old(), which seems to read from the session flashed data, but at this moment I have no idea how to put it there.

Tray2's avatar

In my mind that is making it more complicated than necessary.

Six months from now when you look at that code you have no idea what it does. it's better to utilize the KISS principle and that is using a request to test the result of a request in a view.

SlimDeluxe's avatar

Not sure what you mean, it's a standard Laravel component test and not something I made up :)

I am building a set of reusable components and they need to have their own tests and not be tied to specific controllers. In fact such controllers with all the form components and their variants don't even exist in the package and I don't like the idea of implementing them just for testing.

SlimDeluxe's avatar
SlimDeluxe
OP
Best Answer
Level 4

I found a way - by mocking the Session storage. Please note I am still a Laravel noob, but for now it seems to work and it has not broken any other test.

So, I set the flashed data like this:

protected function mockSessionFlashedData($data)
    {
        $session = Mockery::mock(Session::class);
        $session->shouldReceive('getOldInput')->with(null, null)->andReturn($data);

        foreach ($data as $key => $val) {
            $session->shouldReceive('getOldInput')->with($key, null)->andReturn($val);
        }

        Request::setLaravelSession($session);
    }

and then in the test just use this method. Note that both methods mocks are required, one for the old() and one for old($name). I don't use old($name, $default) because it doesn't work with cleared checkboxes.

I myself put this in a trait called TestsComponents to be used in component tests.

Please or to participate in this conversation.