2

Given the following pest test:

it('allows admins to create courses', function () {
    $admin = User::factory()->admin()->create();
    actingAs($admin);
    $this->get('/courses')->assertDontSee('WebTechnologies');

    $this->followingRedirects()->post('/courses', [
        'course-name' => 'WebTechnologies',
    ])->assertStatus(200)->assertSee('WebTechnologies');
});

The above should fully work; however, the second request post('/courses')... fails saying that:

Failed asserting that <...> contains "WebTechnologies".

If I remove the first request:

it('allows admins to create courses', function () {
    $admin = User::factory()->admin()->create();
    actingAs($admin);

    $this->followingRedirects()->post('/courses', [
        'course-name' => 'WebTechnologies',
    ])->assertStatus(200)->assertSee('WebTechnologies');
});

The test passes.

If I remove the second request instead:

it('allows admins to create courses', function () {
    $admin = User::factory()->admin()->create();
    actingAs($admin);
    $this->get('/courses')->assertDontSee('WebTechnologies');
});

It also passes.

So why should the combination of the two cause them to fail? I feel Laravel is caching the original response, but I can't find anything within the documentation supporting this claim.

apokryfos
  • 38,771
  • 9
  • 70
  • 114
Jazerix
  • 4,729
  • 10
  • 39
  • 71
  • Normally for PHPUnit you'd [reset the database](https://laravel.com/docs/9.x/database-testing#resetting-the-database-after-each-test) before each test to not retain the data. Not sure how you can achieve this in pest. If you are using the pest laravel plugin then you can take a look at [this](https://pestphp.com/docs/plugins/laravel#using-test-traits) – apokryfos Aug 23 '22 at 09:15
  • The database is already being reset between the tests. – Jazerix Aug 23 '22 at 09:19
  • Are you duplicating the uri? Basically you navigated to /courses and then post to /courses. However, is it really /courses/courses when you make the second request? – amac Aug 23 '22 at 09:49
  • @AlexMac I'm following the same standard as laravel proposes [Actions Handled By Resource Controller](https://laravel.com/docs/9.x/controllers#actions-handled-by-resource-controller). A get request to the `/course` is responsible for showing the index page, and a post to the same URI will create a resouce. – Jazerix Aug 23 '22 at 11:17
  • 1
    I think the way I view it is you have a headless browser navigating to your get route and you are looking for something on the page '/courses'. Then while on that page you do a post to courses '/courses/courses'. I could be wrong, but I would try my best to separate those to actions into their own tests. – amac Aug 23 '22 at 11:27
  • @AlexMac, this is also what I'm doing right now. The HTML the error spits out is the correct page, so it's not visiting `/course/course`. General speaking `/` should always refer to the root of a path. I'm fine with having separate tests, however, it's concerning when it's unexplainable why it's behaving as it is. – Jazerix Aug 23 '22 at 11:29
  • 1
    Woah, just for clarity post your route file. – amac Aug 23 '22 at 11:34
  • *"The HTML the error spits out is the correct page"* With or without the WebTechnologies course? – Olivier Aug 26 '22 at 19:03
  • @Olivier without – Jazerix Aug 26 '22 at 23:43
  • You should add some logging to your controllers. It would help understand what is happening. – Olivier Aug 28 '22 at 07:40
  • I am not so sure about the reason, but I had the similar issue some times ago. The reason for this is I think, since the `request` is bound as a singleton to container, and you are running one test instance, it bootstraps the framework and creates a request, but with second call, the same first `request` object is used (because of being singleton) and it fails. Because of this I stopped calling multiple API calls in a single test. – Mohsen Nazari Aug 31 '22 at 08:04

2 Answers2

2

I have created an issue about this on Laravel/Sanctum as my problem was about authentication an stuff...

https://github.com/laravel/sanctum/issues/377

One of the maintainers of Laravel Said:

You can't perform two HTTP requests in the same test method. That's not supported.

I would have wanted a much clearer explanation on why it's not supported.

but I guess, we would never know. (Unless we dive deep into the Laravel framework and trace the request)

UPDATE:

My guess is that, knowing how Laravel works, for each REAL request Laravel initializes a new instance of the APP...

but when it comes to Test, Laravel Initializes the APP for each Test case NOT for each request, There for making the second request not valid.

here is the file that creates the request when doing a test...

vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php

it's on the call method line: 526 (Laravel v9.26.1)

as you can see...

Laravel only uses 1 app instance... not rebuilding the app...

Line 528: $kernel = $this->app->make(HttpKernel::class);

https://laravel.com/docs/9.x/container#the-make-method

the $kernel Variable is an instance of vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php

My guess here is that the HttpKernel::class is a singleton.

P.S. I can do a little more deep dive, but I've procrastinated too much already by answering this question, it was fun thou.

TL;DR.

You can't perform two HTTP requests in the same test method. That's not supported.

UPDATE:

I was not able to stop myself...

I found Laravel initializing Kernel as a singleton

/{probject_dir}/bootstrap/app.php:29-32

Sean Reyes
  • 1,636
  • 11
  • 19
0

Please make sure to not use any classic singleton pattern which isn't invoked with singleton binding or facades. https://laravel.com/docs/9.x/container#binding-a-singleton

$this->app->singleton(Transistor::class, function ($app) {
    return new Transistor($app->make(PodcastParser::class));
});

The Laravel app won't be completely restarted during tests unlike different incoming HTTP requests - even if you call different API endpoints in your tests

SineMatha
  • 74
  • 1
  • 9