PHPUnit: Expected status code 200 but received 419 with Laravel
Asked Answered
T

6

18

I want to test the delete method but I am not getting the expected results from PHPUnit. I receive this message when running the test:

 Expected status code 200 but received 419. Failed asserting that false is true.
 /vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:77
 /tests/Unit/CategoriesControllerTest.php:70

Laravel version: 5.5

Thank you for any help!

Controller constructor:

public function __construct()
{
    $this->middleware('auth');

    $this->middleware('categoryAccess')->except([
        'index',
        'create'
    ]);
}

Controller method:

public function destroy($categoryId)
{
    Category::destroy($categoryId);

    session()->flash('alert-success', 'Category was successfully deleted.');

    return redirect()->action('CategoriesController@index');
}

categoryAccess middleware:

public function handle($request, Closure $next)
{
    $category = Category::find($request->id);

    if (!($category->user_id == Auth::id())) {
        abort(404);
    }

    return $next($request);
}

Category model:

protected $dispatchesEvents = [
    'deleted' => CategoryDeleted::class,
];

Event listener

public function handle(ExpensesUpdated $event)
{
    $category_id = $event->expense->category_id;

    if (Category::find($category_id)) {
        $costs = Category::find($category_id)->expense->sum('cost');

        $category = Category::find($category_id);

        $category->total = $costs;

        $category->save();
    }
}

PHPUnit delete test:

use RefreshDatabase;

protected $user;

public function setUp()
{
   parent::setUp();
   $this->user = factory(User::class)->create();
   $this->actingAs($this->user);
}

/** @test */
public function user_can_destroy()
{
    $category = factory(Category::class)->create([
        'user_id' => $this->user->id
    ]);

    $response = $this->delete('/category/' . $category->id);

    $response->assertStatus(200);

    $response->assertViewIs('category.index');
}
This answered 20/9, 2017 at 15:12 Comment(5)
It's an authentication problem, try with $this->withoutMiddleware(); just to see if it will be OK !!Candlemaker
Hi thanks, I added that to the method and ran the test again and I get this message now: Expected status code 200 but received 302.This
Try this time the treat use WithoutMiddleware; in the PHPUnit Class and don't forget to import it use Illuminate\Foundation\Testing\WithoutMiddleware;Candlemaker
Thank you @Candlemaker $this->withoutMiddleware(); works! The thing is this test is supposed to expect status code 302 I found out by reading an answer from here.This
That's good to know ;)Candlemaker
C
17

Sometimes in testing you will need to disable middlewares to proceed :

use Illuminate\Foundation\Testing\WithoutMiddleware;

class ClassTest extends TestCase
{
    use WithoutMiddleware; // use this trait

    //tests here
}

and if you want to disable them just for one specific test use :

$this->withoutMiddleware();
Candlemaker answered 20/9, 2017 at 15:57 Comment(1)
This works, except in cases where a Middleware is actually part of your tests.Culet
G
55

Solution: When you cached your configuration files you can resolve this issue by running php artisan config:clear.

Explanation: The reason why this can resolve the issue is that PHPUnit will use the cached configuration values instead of the variables defined in your testing environment. As a result, the APP_ENV is not set to testing, and the VerifyCsrfTokenMiddleware will throw a TokenMismatchException (Status code 419).

It won't throw this exception when the APP_ENV is set to testing since the handle method of VerifyCsrfTokenMiddleware checks if you are running unit tests with $this->runningUnitTests().

It is recommended not to cache your configuration in your development environment. When you need to cache your configuration in the environment where you are also running unit tests you could clear your cache manually in your TestCase.php:

use Illuminate\Support\Facades\Artisan; 

public function createApplication()
{
    ....
    Artisan::call('config:clear')
    ....
}

Example based on https://github.com/laravel/framework/issues/13374#issuecomment-239600163

Read more about configuration caching in this blog post or the Laravel documentation.

Gearing answered 16/1, 2018 at 12:41 Comment(2)
This worked for me also, but why does this fix the problem?Hypogeal
@DarrenFindlay I've added some explanation.Gearing
C
17

Sometimes in testing you will need to disable middlewares to proceed :

use Illuminate\Foundation\Testing\WithoutMiddleware;

class ClassTest extends TestCase
{
    use WithoutMiddleware; // use this trait

    //tests here
}

and if you want to disable them just for one specific test use :

$this->withoutMiddleware();
Candlemaker answered 20/9, 2017 at 15:57 Comment(1)
This works, except in cases where a Middleware is actually part of your tests.Culet
F
4

The message here is indeed related to the CSRF middleware. But there is a much better way of attacking this problem than disabling the middleware.

The middleware comes with code built-in that detects if it is being used in a test. This check looks for 2 things:

  • Am I being ran via a command line
  • Am I being ran in an environment type of testing

Default, proper setup of the software correctly causes both flags to be true when running PHP unit. However, the most likely culprit is the value in your APP_ENV. Common ways for this to to be incorrect include:

  • Misconfigured phpunit.xml file. It should contain <server name="APP_ENV" value="testing" />
  • A shell session that has an explicit value set in APP_ENV that overrides this value
  • A Docker/docker-compose/kubernetes session that has an explicit value set in APP_ENV. Seeing about getting this value set via the .env and/or phpunit.xml files is perhaps better if possible. Or ensuring the build/test process sets the value.

This one stumped me as well and I was not convinced that I would need the use of WithoutMiddleware since I did not for a different project, but it turned out I had experimented with something on the command line and overrode APP_ENV in bash.

Farmelo answered 27/5, 2019 at 19:13 Comment(0)
U
4

This is the exact solution:

Laravel environment will set after bootstraping application, so you cant change it from appServiceProvider or another source. for fix this error you need to add this function to App\Http\Middleware\VerifyCsrfToken

public function handle($request, \Closure $next)
    {
        if(env('APP_ENV') !== 'testing')
        {
            return parent::handle($request, $next);
        }

        return $next($request);
    }

you need to use env('APP_ENV') that is set in .env.testing file with

APP_ENV=testing
Uniformed answered 16/12, 2019 at 8:23 Comment(0)
S
1

This is according to my experience, I switch the program to static

php artisan config:cache

php artisan route:cache

and this resulted in csrf running, so I changed it to original mode

php artisan config:clear

php artisan route:clear
Shortsighted answered 6/2 at 16:0 Comment(0)
C
0

modify the file app/Http/Middleware/VerifyCsrfToken.php by adding:

public function handle($request, \Closure $next)
{
    if ('testing' !== app()->environment())
    {
        return parent::handle($request, $next);
    }

    return $next($request);
}

source: https://laracasts.com/discuss/channels/testing/trouble-testing-http-verbs-with-phpunit-in-laravel/replies/29878

Calv answered 26/5, 2018 at 4:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.