Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

eugenf's avatar

[5.0] PHPUnit testing the POST request with csrf

Hi guys.

I'm having a problem testing the POST request in Laravel 5.

This snippet tries to check if the user can subscribe to the newsletter:

$this->call('GET', '/landing/subscribe', [
    'email'  => $this->faker->email,
    '_token' => Session::token(),            
]);

When I run phpunit I get this error:

There was 1 error:

1) LandingControllerTest::it_submits_an_email_to_the_landing_page
Illuminate\Session\TokenMismatchException:

/home/vagrant/www/healthpro/app/Http/Middleware/VerifyCsrfToken.php:25

Here's the method from the middleware that tries to compare the token from the input:

protected function tokensMatch($request)
{
    return $request->session()->token() == $request->input('_token');
}

The problem is that when I make the request the application creates a new session with the new token and I get this Exception.

So here's my question: How can I test the POST requests with csrf?

PS: I call the $this->startSession() in the setUp method to create the session for the testing.

0 likes
14 replies
bashy's avatar

What's the route like to the GET?

MethodNotAllowedHttpException doesn't mean the token was wrong, means the method (GET/POST/etc) wasn't allowed. Normally when trying to POST to a GET route.

eugenf's avatar

The exception was

1) LandingControllerTest::it_submits_an_email_to_the_landing_page
Illuminate\Session\TokenMismatchException:

/home/vagrant/www/healthpro/app/Http/Middleware/VerifyCsrfToken.php:25

I understand why it throws - the session from the test file is different from the one in the request, but I can't figure out how to make them the same.

bashy's avatar

Yeah you've edited it now with a different error.

eugenf's avatar

This is the one I get - from the middleware.

eugenf's avatar

Ok, for those who has the same problem here's the middleware. You should add it to Kernel like this:

<?php namespace App\Http;

use Exception;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {

 /**
  * The application's HTTP middleware stack.
  *
  * @var array
  */
 protected $middleware = [
  'App\Http\Middleware\UnderMaintenance',
  'Illuminate\Cookie\Middleware\EncryptCookies',
  'Illuminate\Cookie\Middleware\AddQueuedCookiesToRequest',
  'Illuminate\Session\Middleware\ReadSession',
  'Illuminate\Session\Middleware\WriteSession',
  'Illuminate\View\Middleware\ShareErrorsFromSession',
        'App\Http\Middleware\ReplaceTestVars',
  'App\Http\Middleware\VerifyCsrfToken',
 ];

 /**
  * Handle an incoming HTTP request.
  *
  * @param \Illuminate\Http\Request $request
  * @return \Illuminate\Http\Response
  */
 public function handle($request)
 {
  try
  {
   return parent::handle($request);
  }
  catch (Exception $e)
  {
   throw $e;
  }
 }

}

And the middleware itself:

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Response;
use Illuminate\Contracts\Routing\Middleware;
use Illuminate\Contracts\Foundation\Application;

class ReplaceTestVars implements Middleware
{
    /**
     * The application implementation.
     *
     * @var Application
     */
    protected $app;

    /**
     * Create a new filter instance.
     *
     * @param Application $app
     * @return \App\Http\Middleware\ReplaceTestVars
     */
    public function __construct(Application $app)
    {
        $this->app = $app;
    }

    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ( 'testing' === $this->app->environment() && $request->has('_token') ) {
            $input = $request->all();
            $input['_token'] = $request->session()->token();
            // we need to update _token value to make sure we get the POST / PUT tests passed.
            $request->replace( $input );
        }

        return $next($request);
    }

}
3 likes
edbizarro's avatar

i had the same error a while ago, my workaround

public function testFormSubmitAuthError() {

        Session::start();

        $credentials = array(
            'username' => 'wronguser',
            'password' => 'wrongpass',
            '_token' => csrf_token()
        );

        $this->call('POST', '/auth/login', $credentials);

        $this->assertResponseOk();
        $this->assertViewHas('userToken', null);

    }
5 likes
Glutnix's avatar

I was wanting to use PHPUnit and TestCase with a JSON API endpoint, and it was failing on CSRF verification. I needed to actually USE the trait I was including

use Illuminate\Foundation\Testing\WithoutMiddleware; // include the trait
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class LoginTest extends TestCase
{
    use WithoutMiddleware; // use the trait

    /**
     * @return void
     * @test
     */
    public function it_rejects_a_bad_auth_attempt()
    {
        $credentials = array(
            'email' => 'bad@test.com',
            'password' => 'wrong',
        );

        $this->post('/login', $credentials)
            ->seeJson(['authenticated' => false])
            ->assertResponseStatus(401);
    }
}
3 likes
iraklisg's avatar

@Glutnix Indeed using the WithoutMiddleware trait we may disable all middlewares for the routes specified in the test class and thus successfully skip the CSRF verification culprit.

However, In case someone wants to disable the middlewares only for a specific test function, the $this->withoutMiddleware(); helper function might come in handy

4 likes
mushood's avatar

@edbizarro Answer worked for me, but I wasted a couple of minutes to realise that I had to include the

Session::start();

and of course, dont forget the use

use Illuminate\Support\Facades\Session;

In case anyone comes across this.

2 likes
badigra's avatar

There are something you should know when disabling ALL middleware in your tests: if you are using Route Model Binding it will not work, because it's functionality depends on its one middleware SubstituteBindings::class

So if your goal is only disabling CSRF you can use parameter like this:

$this->withoutMiddleware(VerifyCsrfToken::class);
2 likes
tavo1987's avatar

Hi, I've had the same error when using Homestead.

I've solved this error editing my Homestead.yaml file.

I've commented the section where set the global environment viriables for Homestead.

#variables:
#    - key: APP_ENV
#      value: local

For this reason, Laravel couldn't set the configuration environment to testing , and automatically disable VerifyCsrfToken Middleware in my tests.

After updating the Homestead.yaml, don't forget re-provision the machine by running:

vagrant reload --provision

This will update the PHP-FPM configuration for all of the installed PHP versions and also update the environment for the vagrant user

ees's avatar

In my case it was a phpunit configuration problem. I was not using the default laravel phpunit.xml.

My APP_ENV was local instead of testing. No need of middleware disabling for me.

testing#environment

1 like

Please or to participate in this conversation.