Posts | About

Laravel Feature Tests With MySQL In Docker

May 21, 2020 by Areg Sarkissian

Feature tests using a production database

In the blog post My Laravel Local Docker Services Configuration I described how I use MySQL running in docker for local development.

In this article I will show you how to use a MySQL test database server running in a docker container to run your Laravel phpunit tests against.

We will set up a separate MySQL server instance used for testing that will run in its own separate docker container . This way our test database will be completely separate from our development database to avoid impacting the development database when running tests.

To setup a new MySQL docker container we can duplicate the existing MySQL service in the docker compose file and just give it a different port mapping.

Here is the docker compose file with the two MySQL services:

version: "3.1"
services:
    # this is the application database server used when running the app locally
    myapp-mysql:
      image: mysql:8.0
      container_name: app-mysql
      # persist data to data/mysql directory to save data accross container runs
      volumes:
        - ./data/mysql:/var/lib/mysql
      environment:
        - MYSQL_ROOT_PASSWORD="${DB_PASSWORD}"
        # upon container first run a database with the name of MYSQL_DATABASE setting will be created
        - MYSQL_DATABASE="${DB_DATABASE}"
        - MYSQL_USER="${DB_USERNAME}"
        - MYSQL_PASSWORD=${DB_PASSWORD}"
      ports:
        # connect to the application server through port 8001 on localhost
        - "8001:3306"

    # this is the test database server used to run features tests against
    myapp-mysql-test:
      image: mysql:8.0
      container_name: app-mysql-test
      # no volume mapping required since we dont need the data to persist after container is shut down
      environment:
        - MYSQL_ROOT_PASSWORD="${DB_PASSWORD}"
        # upon container first run a database with the name of MYSQL_DATABASE setting will be created
        - MYSQL_DATABASE="${DB_DATABASE}"
        - MYSQL_USER="${DB_USERNAME}"
        - MYSQL_PASSWORD="${DB_PASSWORD}"
      ports:
        # connect to the test server through port 8011 on localhost
        - "8011:3306"

And here is the related environment variable settings from the .env file:

DB_DATABASE=myapp
DB_USERNAME=myapp
DB_PASSWORD=myapp
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8001

Note the DB_PORT is currently set to the value of the development database port mapping.

When we run our phpunit tests on our local host we will connect to the port mapped to the myapp-mysql-test test database.

Connecting to the test database

If we look in the config/database.php file we can see that at the moment we only have one MySQL connection named mysql. This connection is the default database connection that the application uses and the database migrations run against.

The default database connection is specified in the config/database.php file as shows below:

'default' => env('DB_CONNECTION', 'mysql')

During execution of feature tests we need to apply migrations to the test database then run the tests against the test database by overriding the DB_PORT setting.

During execution of feature tests when the RefreshDatabase or RunsMigrations traits are used to run database migrations the default connection is used. Also when we run php the artisan migrate command the default connection is used.

Similarly during our unit tests, we test application code that uses Laravels Eloquent ORM which uses the default connection.

The default connection is set to use the mysql connection which is configured to use the DB_PORT setting value.

After the DB_PORT setting is overridden, the mysql default database connection will connect to the test database thereby running the migrations and tests against the test database.

There are two approaches we can use run tests and migrations against the test database. Both approaches override the DB_PORT setting to switch the port value from 8001 to 8011 to connect to the test database server.

Approach 1 - Overriding DB_PORT setting in the phpunit.xml file

The phpunit.xml file has a php section where you can specify any environment settings you want to override before running tests.

Below is the out of the box overrides defined in phpunit.xml after a new Laravel project installation:

<php>
    <server name="APP_ENV" value="testing"/>
    <server name="BCRYPT_ROUNDS" value="4"/>
    <server name="CACHE_DRIVER" value="array"/>
    <server name="DB_CONNECTION" value="sqlite"/>
    <server name="MAIL_MAILER" value="array"/>
    <server name="QUEUE_CONNECTION" value="sync"/>
    <server name="SESSION_DRIVER" value="array"/>
</php>

So by adding a <server name="DB_PORT" value="8011"/> element we can override the DB_PORT setting specified in the .env file.

The overridden setting will then be used when running the migrations and unit tests.

Approach 2 - Overriding DB_PORT setting in a .env.testing file

If we specify a .env.testing file in the root directory of our project, phpunit will use that file instead of the standard .env file to set the environment variable values when running tests.

So we can override the port value by copying the .env file to a .env.testing file and change the DB_PORT value to 8011 in the .env.testing file:

DB_PORT=8011

Note: We can change other settings in the .env.testing file as well if we need to. For example we can change the cache driver to array and session driver to array to test agains in memory cache and session stores.

It is important to note that the overrides section in phpunit.xml will now override the settings in the .env.testing file instead of the .env file. So we need to remove those overrides from phpunit.xml since we do not want them to be overridden.

Choosing the approach to override the DB_PORT setting

Normally in the unit test classes we use the Laravel RefreshDatabase trait to migrate the database as part of the unit tests.

When doing parallel testing, this approach has issues. In the Laravel Run Parallel Tests With MySQL In Docker blog post I describe how to overcome those issues and why we need to use the .env.testing file.

The approach of using the .env.testing file is useful when we need to run tests in parallel. This is because we can not use the RefreshDatabase trait when running tests in parallel and therefore need to run the database migrations outside the unit tests by using the artisan migrate command.

For the artisan migrate command to be able to run against the test database we need to override the DB_PORT setting that the migrate command uses. We do so by passing the separate .env.testing file name, that overrides the DB_PORT setting value, as an argument to the migrate command.

If you do not need to run tests in parallel, then the approach of using the phpunit.xml file to override the DB_PORT value is simpler as you would not have to keep the .env.testing file in sync with other changes to the .env file.

Setting up phpunit to use RefreshDatabase trait

If we are not running tests in parallel, we need to use the RefreshDatabase trait in our phpunit classes so that the test database is refreshed after each test.

In order to not repeat ourselves in every test class we can extend the base Tests\TestCase phpunit class and add the trait there. Then our test classes can extend the derived TestCase class to take advantage of the RefreshDatabase trait.

namespace Tests\Feature;

use Tests\TestCase as BaseTestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

abstract class TestCase extends BaseTestCase
{
    use RefreshDatabase;
}