Laravel × Docker でテスト用のデータベースコンテナを使う

新しい記事を書きました！
[Docker × Laravel テスト用のデータベースコンテナを構築する](https://qiita.com/ucan-lab/items/42c1814d8bd69895374c)

---

[Laravel2 Advent Calendar 2019 - Qiita](https://qiita.com/advent-calendar/2019/laravel-2) の 16日目 の記事です。

## Laravel環境構築

[ucan-lab/docker-laravel-alpine](https://github.com/ucan-lab/docker-laravel-alpine) を元にLaravel環境を作ります。

```shell-session
$ git clone git@github.com:ucan-lab/docker-laravel-alpine.git
$ cd docker-laravel-alpine
$ make create-project
```

### データベースコンテナ構成

こちらの [docker-compose.yml](https://github.com/ucan-lab/docker-laravel-alpine/blob/master/docker-compose.yml) から抜粋です。

```yaml:docker-coompose.yml
  db:
    image: mysql:8.0
    volumes:
      - db-store:/var/lib/mysql
      - ./logs:/var/log/mysql
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}
    ports:
      - ${DB_PORT}:3306

  db-testing:
    image: mysql:8.0
    volumes:
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    tmpfs:
      - /var/lib/mysql
      - /var/log/mysql
    environment:
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}
    ports:
      - ${DB_TESTING_PORT}:3306
```

`db` コンテナが通常の開発用DBコンテナ
`db-testing` コンテナがテスト用DBコンテナ

## phpunit.xml

`vendor/bin/phpunit` を実行すると何も指定しない場合 `phpunit.xml` が読み込まれます。

```xml:phpunit.xml
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="MAIL_DRIVER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
    </php>
```

`<server>` タグを `<env>` タグに書き換えます。

```xml:phpunit.xml
    <php>
        <env name="APP_ENV" value="testing" force="true"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="MAIL_DRIVER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
        <env name="SESSION_DRIVER" value="array"/>
    </php>
```

`APP_ENV` を `docker-compose.yml` にて環境変数として定義していますので、
`phpunit.xml` で既存の環境変数の上書きが必要です。

`phpunit.xml` で`env`タグや`server`タグを設定すると次のようなPHPコードになるイメージです。

```php
$_ENV['APP_ENV'] = 'testing'; // env タグ
$_SERVER['APP_ENV'] = 'testing'; // server タグ
```

Dockerを使ってるとココは結構ハマりどころかなと思います。

## CreatesApplicationトレイトで一度だけマイグレーションを実行する

テストを書く際は [TestCase](https://github.com/laravel/laravel/blob/master/tests/TestCase.php) を継承してテストを作ります。

`TestCase` クラスは [CreatesApplication](https://github.com/laravel/laravel/blob/master/tests/CreatesApplication.php) トレイトをインポートしてます。

この `createApplication()` メソッドは各テスト実行の際に必ず呼ばれるのでこのタイミングで初回のみテスト用データベースのマイグレーションを実行します。

```php:tests/CreatesApplication.php
<?php declare(strict_types=1);

namespace Tests;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\Artisan;

trait CreatesApplication
{
    /**
     * @var bool
     */
    protected $isSetUpDatabase = false;

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->make(Kernel::class)->bootstrap();

        $this->setUpDatabase();

        return $app;
    }

    /**
     * Run migration once
     */
    protected function setUpDatabase(): void
    {
        if ($this->isSetUpDatabase) {
            return;
        }

        Artisan::call('migrate:fresh');

        $this->isSetUpDatabase = true;
    }
}
```

## テスト実行

```
$ make app
$ ./vendor/bin/phpunit
```

## 参考

- https://phpunit.readthedocs.io/ja/latest/configuration.html
- https://readouble.com/laravel/6.x/ja/testing.html

## 関連記事

- [Laravel マイグレーションのロールバックテスト](https://qiita.com/ucan-lab/items/6a5f4acf70c889a0093a)