Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Laravel phpunit test is failing. Going to an unexpected page. Only happens in the test

So, I have a page with a button on it with the value "Create". When I click that Create button, without filling out any of the fields, it validates the form and displays error messages on the same page. When I do that in the browser, it works fine, but when I do it with phpunit, it has unexpected results and I do not know why.

Here is my integration test:

public function testCreateValidation()                                     
{ 
    $this->visit(route('patients.indexes.create', $this->patient->id));    
    $this->press('Create');                                                
    $this->seePageIs(route('patients.indexes.create', $this->patient->id));              
}   

And this is the result:

There was 1 failure:
1) Tests\Integration\IndexControllerTest::testCreateValidation
Did not land on expected page [http://localhost/patients/69/indexes/create].

Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'http://localhost/patients/69/indexes/create'
+'http://localhost/patients'

/vagrant/vendor/laravel/framework/src/Illuminate/Foundation/Testing/InteractsWithPages.php:141
/vagrant/tests/Integration/IndexControllerTest.php:51

I don't understand why it is being redirected to the patients page.

Here is the Laravel create method that is being tested:

public function create($id)                                                         
{                                                                                   
    $index = $this->indexes->newInstance();                                         
    $patient = $this->patients->findOrFail($id);                                    

    return view('patient.index.create', ['index' => $index, 'patient' => $patient]);
} 

And here is the relevant section of the create view:

<?= Form::open(['route' => array('patients.indexes.store', $patient->id), 'class' => 'form-horizontal']) ?>
    @include('patient.index._form')
    <?= Form::submit('Create', ['class' => 'btn btn-primary']) ?>
<?= Form::close() ?> 

And finally the store method that it is being sent to:

public function store(IndexRequest $request, $id)       
{                                                       
    $index = $this->indexes->newInstance();             
    $index->fill($request->all());                      
    $index->patient_id = $id;                           
    $index->save();                                     

    $patient = $index->patient;                         

    return redirect()->route('patients.edit', $patient);
} 

I am also using a FormRequest to validate the input:

public function rules()                   
{                                         
    return [                              
        'index_title' => 'required',      
        'index_description' => 'required',
    ];                                    
}  

So essentially, since it is failing the validation in the IndexRequest, the IndexRequest should kick it back to the patients.indexes.create view and display errors. But for some reason it's being kicked to the patients page (this ONLY happens in the test, if I try it out by manually clicking the Create button in the browser, it works as expected)

I've had this issue before but have never been able to solve it. Any ideas?

like image 389
jasonaburton Avatar asked Jan 29 '16 15:01

jasonaburton


2 Answers

It sounds like you're having CSRF issues. When you navigate to the form in your browser, Laravel stores a special token in your browser's cookies and in the request headers. Then, when you submit the form it looks for that token to make sure you are the one submitting the form.

When you test with PHPUnit, that token isn't sent, so you either have to send it yourself, or you have to exclude your tests from the middleware that checks for the token. Excluding your tests is the easier method.

In Laravel 5.0 I did that by overriding the handle() method in the VerifyCsrfToken middleware.

class VerifyCsrfToken extends BaseVerifier {
    // Override the handle() method in the BaseVerifier class
    public function handle($request, Closure $next) {
        // When the App environment is 'testing', skip CSRF validation.
        if (app()->environment() === 'testing') {
            return $next($request);
        }

        return parent::handle($request, $next);
    }
}

By default, PHPUnit pulls the environment variables from the phpunit.xml file in your Laravel site root. The APP_ENV variable is the one that controls the application environment name, and it defaults to 'testing'. If yours is different, you then need to change the code I provided to match it.

like image 180
BrokenBinary Avatar answered Oct 05 '22 23:10

BrokenBinary


If middleware is in fact the problem you can exclude it from the tests using the WithoutMiddleware trait:

class IndexControllerTest extends TestCase
{
    use Illuminate\Foundation\Testing\WithoutMiddleware;
    ...
like image 20
jclyons52 Avatar answered Oct 05 '22 23:10

jclyons52