How to start writing tests for Laravel apps with in-memory SQLite

In this post, I’ll write about how to configure Laravel apps for PHPUnit, and how to write basic tests.

This post is meant to be a note for myself, but hopefully someone finds it helpful too.

DB Driver

First of all, this approach requires SQLite3 driver. So, you have to make sure that PHP on your machine has SQLite3 extension. If you’re using Windows and PHP 7, you would probably have to open php.ini and add or uncomment the following lines, unless you’ve already done it.

extension=php_pdo_sqlite.dll
extension=php_sqlite3.dll

extension_dir = <php installation directory>/PHP7/ext

Configuration

Then, configure phpunit.xml in order to use in-memory SQLite DB.

The objective is to have PHPUnit create mock data in memory when it does CRUD operations. Mocking data in memory for testing is much faster than using actual data in your DB.

phpunit.xml

    <!-- use this if you're using Laravel 6 -->
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <!-- add these two lines -->
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
        <!---->
        <server name="MAIL_DRIVER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
    </php>

NOTE: This example is using Laravel 6.2. If you are using Laravel 5, you’ll see <env> tags instead of <server> tags.

For example, here is how phpunit.xml looks like in my Laravel 5.5 app.

    <!-- use this if you're using Laravel 5 -->
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="DB_CONNECTION" value="sqlite"/>
        <env name="DB_DATABASE" value=":memory:"/>
        <env name="QUEUE_DRIVER" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
    </php>

Then, run php artisan config:clear.

Then, make sure that config/database.php is using env(‘DB_DATABASE’). (This is actually the default setting of the file. So, you won’t have to do anything if you haven’t touched it before)

'connections' => [

        'sqlite' => [
            'driver' => 'sqlite',
            'url' => env('DATABASE_URL'),
            'database' => env('DB_DATABASE', database_path('database.sqlite')),
            'prefix' => '',
            'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
        ],

// ... and so on

Then, test if your Laravel app runs tests on memories for database. Open Terminal, run vendor/bin/phpunit and see if DB tables are still there.

Tests

For each test class that interacts with DB, use DatabaseMigrations trait. This trait allows PHPUnit to…

  1. Rollback migration after each test
  2. And migrate data before each test

Here is a simple example of testing a REST API route which is supposed to return all customer objects.

php artisan make:test CustomerControllerTest --unit
<?php

namespace Tests\Unit;

use App\Customer;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;

class CustomerControllerTest extends TestCase
{
    use DatabaseMigrations;

    /** @test */
    public function canGetAllCustomers()
    {
        foreach (range(1, 1000) as $i) {
            factory(Customer::class)->create();
        }

        $res = $this->get('/api/customers');

        $res->assertStatus(201)
            ->assertJson([
                'data' => Customer::all()->toArray(),
            ]);
    }
}

As mentioned above, each time a test runs, PHPUnit will roll back and migrate data. So, in order to run a test like this, you need migrations files.

php artisan make:migration create_customers_table --create=customers

Also, in the above example, I put a model class Customer and used Laravel’s factory method. So, you need to create a class and factory for them too.

php artisan make:model Customer
php artisan make:factory CustomerFactory

Leave a Reply

Your email address will not be published. Required fields are marked *