Docker × Laravel テスト用のデータベースコンテナを構築する

Laravelでデータベースのテストを行う時、`RefreshDatabase`や`factory`など便利なメソッドやトレイトが用意されています。
これらを使ってテストを行いますが、Dockerでデータベースコンテナ一つでやると開発で使ってるデータがクリアされてしまってあまりよろしくないです。

今回はテストする時はテスト用のデータベースを使うように設定します。

## 前提

- [最強のLaravel開発環境をDockerを使って構築する【新編集版】](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)
- https://github.com/ucan-lab/docker-laravel

当記事は上記の記事の補足になる記事です。

## Laravel環境構築

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

## 環境

- PHP 7.4.4
- Laravel 7.27.0

## 手順

### docker-compose.yml を編集する

```shell-session
$ docker-compose down
```

※`docker-compose.yml` や `Dockerfile` を変更する場合は予めコンテナを破棄しておくと良いです。

```yml:docker-compose.yml
  db-testing:
    build: ./infra/docker/mysql
```

`db-testing` サービスの設定を `services` 配下に追記します。

```shell-session
$ docker-compose up -d
```

コンテナを作成して起動します。

```shell-session
$ docker-compose ps
           Name                          Command              State                 Ports              
-------------------------------------------------------------------------------------------------------
docker-laravel_app_1          docker-php-entrypoint php-fpm   Up      9000/tcp                         
docker-laravel_db-testing_1   docker-entrypoint.sh mysqld     Up      3306/tcp, 33060/tcp              
docker-laravel_db_1           docker-entrypoint.sh mysqld     Up      0.0.0.0:3306->3306/tcp, 33060/tcp
docker-laravel_web_1          nginx -g daemon off;            Up      0.0.0.0:80->80/tcp
```

`db-testing` のコンテナが起動していればokです。

## .env.testing

```shell-session
$ cp .env.example .env.testing
$ php artisan key:generate --env=testing
```

```:.env.testing
DB_CONNECTION=mysql
DB_HOST=db-testing
DB_PORT=3306
DB_DATABASE=laravel_local
DB_USERNAME=phper
DB_PASSWORD=secret
```

## サンプルのテストを作成

```shell-session
$ php artisan make:test SampleTest
```

`tests/Feature/SampleTest.php` ファイルが生成されるので、下記のように書き換えます。

```php:backend/tests/Feature/SampleTest.php
<?php declare(strict_types=1);

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\User;

class SampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A basic feature test example.
     *
     * @return void
     */
    public function testExample()
    {
        factory(User::class)->create(['name' => 'sample']);
        $this->assertEquals(1, User::where('name', 'sample')->count());
    }
}
```

## テスト実行

```shell-session
$ php artisan test
   PASS  Tests\Unit\ExampleTest
  ✓ basic test

   PASS  Tests\Feature\ExampleTest
  ✓ basic test

   PASS  Tests\Feature\SampleTest
  ✓ example

  Tests:  3 passed
  Time:   3.74s
```

テストが通ったことを確認できればokです。
`db` データベースのデータも消えていないことを確認してください。

## 補足: 開発用データベースが消える場合1

テスト用のデータベースコンテナを参照してくれない場合は、設定ファイルがキャッシュされている可能性があります。

```shell-session
$ php artisan config:clear
```

忘れ防止のため、composerスクリプトに入れるのもいいかもしれません。

```json:composer.json
{
    "scripts": {
        "test": [
            "@php artisan config:clear",
            "@php artisan test"
        ]
    }
}
```

```shell-session
$ composer test
```

## 補足: 開発用データベースが消える場合2

`docker-compose.yml` の `environment` でサーバー環境変数にデータベースの接続情報等を設定している場合です。
`.env.testing` よりもサーバー環境変数が優先されてしまいます。

対策としては、 `phpunit.xml` で環境変数を上書きできます。

```xml:phpunit.xml
    <php>
        <env name="APP_ENV" value="testing" force="true"/>
        <env name="DB_CONNECTION" value="mysql" force="true"/>
        <env name="DB_HOST" value="db-testing" force="true"/>
        <env name="DB_PORT" value="3306" force="true"/>
        <env name="DB_DATABASE" value="laravel" force="true"/>
        <env name="DB_USERNAME" value="phper" force="true"/>
        <env name="DB_PASSWORD" value="secret" 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>
```

注意としては、 `server` タグではなく、 `env` タグで指定します。

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

## 参考

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

## 過去記事

以前に似た記事を書いてました...

- [Laravel × Docker でテスト用のデータベースコンテナを使う](https://qiita.com/ucan-lab/items/0f7100f08a062e892c52)