NathanIsaac's avatar

PHPUnit Test Setting Cookies

// Routes
Route::get('set-cookie', function() {
    return redirect('view-cookie')->withCookie(cookie('test', 'hello'));
});

Route::get('view-cookie', function(Request $request) {
    return $request->cookie('test');
});
/** @test */
public function it_saves_a_cookie()
{
    $this->visit('/set-cookie')
        ->seePageIs('/view-cookie')
        ->seeCookie('test', 'hello');
}
Starting test 'SignInTest::it_saves_a_cookie'.
F

Time: 388 ms, Memory: 10.00Mb

There was 1 failure:

1) SignInTest::it_saves_a_cookie
Cookie [test] not present on response.
Failed asserting that false is true.

Any thoughts on why this is not returning the cookie? If I view the page in the browser it does return the cookie but in the test it does not.

For other tests that require that cookie to be present should I mock the cookie. If so, what is the best method of doing so?

Thanks in advance for the help.

0 likes
15 replies
Asva's avatar

It very much appears to be a bug. Here's my testcase:

$this->get('register/311');
$this->seeCookie('referral'); // Passes
$this->get('register/311')->followRedirects();
$this->seeCookie('referral'); // Fails

It seems, laravel doesn't persist cookie between request. The only possibility I'm seeing now is to manually redirect and pass cookie. Awful, I know.

bobbybouwmann's avatar

Well it might not be a bug in Laravel. If you want to use a cookie in the next request you need to give that to the request. So if you then refresh, the cookie is gone since you came from another request.

@Asva have you tried it with a cookie that stays forever?

$response->withCookie(cookie()->forever('name', 'value'));
Asva's avatar

@bobbybouwmann

If you want to use a cookie in the next request you need to give that to the request

The problem is doing so is not exactly the easiest thing in the world (C). While plain Guzzle defaults to saving cookies (as it should be).

have you tried it with a cookie that stays forever?

Yep, tried a lot of things actually. Turning off encryption included.

bobbybouwmann's avatar

Mmh, that makes it pretty difficult to test... Not sure how you can get around this!

ifpingram's avatar
Level 4

@Asva you need to adjust the tests slightly. The following works:

    /** @test */
    public function it_saves_a_cookie()
    {
        $this->call('GET', 'set-cookie');

        $this->assertRedirectedToRoute('view.cookie');

        $this->seeCookie('test', 'hello');
    }
// Routes
use Illuminate\Http\Request;

Route::get('set-cookie', function() {
    return Redirect::route('view.cookie')->withCookie(cookie('test', 'hello'));
});

Route::get('view-cookie', ['as' => 'view.cookie', function(Request $request) {
    return $request->cookie('test');
}]);

It doesn't work when you use the fluent interface chaining of the tests; I presume because there is more than one request-response object needed.

Good luck!

1 like
Bloomanity's avatar

So there is no option to test cookies are being set in subsequent requests if the $minutes value is high enough?

ifpingram's avatar

@Bloomanity there is no need; this is not testing, it is using :)

A test that spans multiple requests should be broken down into its individual request cycles to test each one separately.

Asva's avatar

@ifpingram actually, what you wrote is my 1st test. It passes and that's fine. Here's the second one

$cookie = cookie('referral', '311');
$this->call('post', 'api/register', $this->userData, [$cookie]);
$user = $this->getRegisteredUser();
$this->assertEquals('311', $user->referral_id);

This one doesn't work (though feature is implemented). In controller I'm not seeing any cookie.

And it'll be fun and wise for integration test to store cookies. Maybe as an option or something. I'll probably file an issue in a few days.

UPD: Created an issue.

ifpingram's avatar

@Asva The bug you have posted is not a bug, but instead your test is incorrect.

The cookie should be passed as an array of values, not as a Symfony\Component\HttpFoundation\Cookie instance. Test should be changed to read:

    public function test_cookie_passed_to_request(){
        $cookie = ['name' => Crypt::encrypt('value')];

        $this->call('GET', 'test', [], $cookie);

        $this->assertEquals('value', $this->response->content());
    }
Asva's avatar

@ifpingram Thank you very much for that solution :3. I was being ripping my hair, trying to make that one green.

And I did some more digging:

$cookie1 = ['name1' => Crypt::encrypt('value1')];
$cookie2 = ['name2' => Crypt::encrypt('value2')];
$this->call('get', 'test', [], [$cookie1, $cookie2]);
$this->assertEquals('value1', $this->response->content());

That test fails. And here is method definition from src:

public function call($method, $uri, $parameters = [], $cookies = [], ...

It says $cookies and not cookie. Yet it accepts only one cookie and that one should be weirdly parsed.

I will probably stay on that issue for a little bit longer. That kind of interface is not suitable for common developer. Even armed with xdebug I wasn't able to figure things out.

ifpingram's avatar

@Asva Try this to check the value of the first cookie.

Route::get('test', [
    'middleware' => 'web',
    function (Request $request) {
        return $request->cookie('name1');
    }
]);
    public function test_cookies_passed_to_request()
    {
        $cookies = [
            'name1' => Crypt::encrypt('value1'),
            'name2' => Crypt::encrypt('value2')
        ];
        $this->call('get', 'test', [], $cookies);
        $this->assertEquals('value1', $this->response->content());
    }

You wrapped an extra array around the cookies which was not necessary.

Asva's avatar

@ifpingram Thanks for your help again. I got it now. Forgot that cookies for HTTP request have only name and value. I expected to pass down expire date and such, for some reason.

And at least greeen! And mostly sane. Here's the test:

public function test_cookie_passed_to_request()
{
    $this->disableCookiesEncryption('name');
    $cookie = [ 'name' => 'value' ];
    $this->call('get', 'test', [ ], $cookie);
    $this->assertEquals('value', $this->response->content());
}

The route:

Route::get('test', [
    'middleware' => 'web',
    function (Request $request) {
        return $request->cookie('name');
    }
]);

TestCase function to disable cookie encryption:

/**
 * @param array|string $cookies
 * @return $this
 */
protected function disableCookiesEncryption($cookies)
{
    $this->app->resolving(EncryptCookies::class,
        function ($object) use ($cookies) {
            $object->disableFor($cookies);
        });

    return $this;
}
1 like
cristo's avatar

@Asva You don't have to disable cookie encrypt, you can instead encrypt the cookie value: $cookie = ['name' => Crypt::encrypt('value')]

1 like

Please or to participate in this conversation.