Better Integration Testing in Laravel 5.1: DatabaseMigrations, DatabaseTransactions, and WithoutMiddleware

Posted on June 19, 2015 | By Matt Stauffer


Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

Continuing in our series about testing and integration testing in Laravel 5.1, let's take a look at some new traits that are available for your tests.

Database migrations and data

Two of these traits have to do with the behavior of database migrations and database state during your testing. If you have never tested anything that relies on the database, it'll help you to know that it can be hard at times to ensure that your database gets into the right state before your tests run.

DatabaseMigrations Trait

Here's how it normally goes: Before every test, you migrate your database tables. And after each test, you either wipe out the data that was added in the test, or if you're asking for pain, you don't and you hope that data doesn't break any tests that happen later in the testing process.

The DatabaseMigrations trait simplifies this process for you. Here's the source for the trait:

<?php

namespace Illuminate\Foundation\Testing;

trait DatabaseMigrations
{
    /**
     * @before
     */
    public function runDatabaseMigrations()
    {
        $this->artisan('migrate');

        $this->beforeApplicationDestroyed(function () {
            $this->artisan('migrate:rollback');
        });
    }
}

As you can see, it just migrates your data on setup, and then registers a direction to roll back the migrations when the application is torn down at the end of the test.

How do you use it?

<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

    //
}

That's it!

DatabaseTransactions Trait

Migrating up and down every test is a simple way to handle it, but it's hard to do that quickly unless you're working with SQLite. We do a lot of SQLite-based testing at Tighten, but it can be irritating at times to worry about some of SQLite's restrictions every time you write a migration.

If you want to test in MySQL, you might find more success wrapping each test in a transaction. It functions like this:

Every time a test is set up, it starts a database transaction. The test runs. And when the test is being torn down, the transaction is rolled back, which means the database is nice and pristine again.

Let's check out the source of the trait:

<?php

namespace Illuminate\Foundation\Testing;

trait DatabaseTransactions
{
    /**
     * @before
     */
    public function beginDatabaseTransaction()
    {
        $this->app->make('db')->beginTransaction();

        $this->beforeApplicationDestroyed(function () {
            $this->app->make('db')->rollBack();
        });
    }
}

Just like above, it's running the "begin" operation, and then registering a callback to run the "stop" operation when the test is spinning down.

<?php

use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    use DatabaseTransactions;

    //
}

Testing routes with middleware

In Laravel 5, CSRF middleware changed to be enabled by default on all routes. But you don't always want to worry about CSRF in the middle of a test. Or maybe you have some other custom middleware that you don't want to run.

There's now a trait you can add to your tests to turn off that Middleware so you don't have to worry about it in your tests:

WithoutMiddleware Trait

You guessed it, we're going to start by looking at the source:

<?php

namespace Illuminate\Foundation\Testing;

use Exception;

trait WithoutMiddleware
{
    /**
     * @before
     */
    public function disableMiddlewareForAllTests()
    {
        if (method_exists($this, 'withoutMiddleware')) {
            $this->withoutMiddleware();
        } else {
            throw new Exception('Unable to disable middleware. CrawlerTrait not used.');
        }
    }
}

We're calling the withoutMiddleware method if it exists. It's checking to make sure we're in a test that has CrawlerTrait, which practically is just checking if we are working with integration tests by extending Laravel's TestCase. And if so, it's disabling all middleware for these tests.

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;

class ExampleTest extends TestCase
{
    use WithoutMiddleware;

    //
}

Note that you can also just call $this->withoutMiddleware(); in a single test method if you only want to disable it within that method.

Concladadore

That's it. Three simple helper traits to make it even simpler to do integration testing in Laravel 5.1.


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  laravel 5.1  •  testing  •  integration testing