複数の DB コネクションを使ってる場合に RefreshDatabase が失敗する

MySQL と PostgreSQL など、Laravel で複数のコネクションを使ってるときに `RefreshDatabase` を使ったテストを書くと、二度目以降の実行でエラーになります。

調べてみると、その原因は `RefreshDatabase` で使われている `migrate:fresh` にあることが分かりました。[^1]
`migrate:fresh` では `db:wipe` と `migrate` の 2 つのコマンドが呼ばれているのですが、`db:wipe` がデフォルトのコネクションに対してしか実行されないのです。[^2]

`RefreshDatabase` のコードを見てみると、以下のような使用するコネクションを指定するための仕組みが用意されており、これを使えばうまくいくような気がしたんですけど、ここで指定したコネクションは、トランザクションでのみ使われるようでした。[^3]
`DatabaseTransactions` にもこれと同じコードがあるので、そちらから派生させたときの名残なんでしょうね。[^4]

```php:src/Illuminate/Foundation/Testing/RefreshDatabase.php
    /**
     * The database connections that should have transactions.
     *
     * @return array
     */
    protected function connectionsToTransact()
    {
        return property_exists($this, 'connectionsToTransact')
                            ? $this->connectionsToTransact : [null];
    }
```

じゃあ `migrate:fresh` の方でもこの値を使うようにしたいと思ったのですが、`migrate:fresh` では `db:wipe` と `migrate` がセットになってしまっているため、`db:wipe` を 2 回実行するようにすると、`migrate` まで 2 回実行されてしまいます。おのれ密結合。

しかも、`migarate` の `--database` オプションは `migrations` テーブルを作るデータベースを指定するためのものなので、たとえ複数のコネクションを使っていたとしても、その中からひとつだけ指定すればよいものです。
一方で、`db:wipe` の `--database` オプションはテーブルを削除するデータベースを指定するためのものなので、複数コネクションを使っている場合は、その全てを指定する必要があります。
というように、それぞれ意味が違うのですが、`migrate:fresh` では `--database` オプションで受け取った値を、その両方に渡してしまっています。[^2]

そのため対策としては、`migrate:fresh` を使うのをやめて、代わりに `migrate:refresh` を使うようにするか、`db:wipe` と `migrate` を自分で呼ぶようにするしかなさそうです。
以下に実装例を挙げておきます。

```php:tests/RefreshDatabase.php（refreshを使うパターン）
<?php

namespace Tests;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Testing\RefreshDatabase as BaseTrait;
use Illuminate\Foundation\Testing\RefreshDatabaseState;

trait RefreshDatabase
{
    use BaseTrait;

    /**
     * Refresh a conventional test database.
     *
     * @return void
     */
    protected function refreshTestDatabase()
    {
        if (! RefreshDatabaseState::$migrated) {
            $this->artisan('migrate:refresh');

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

        $this->beginDatabaseTransaction();
    }
}
```

```php:tests/RefreshDatabase.php（wipeとmigrateを自分で呼ぶパターン）
<?php

namespace Tests;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Testing\RefreshDatabase as BaseTrait;
use Illuminate\Foundation\Testing\RefreshDatabaseState;

trait RefreshDatabase
{
    use BaseTrait;

    /**
     * Refresh a conventional test database.
     *
     * @return void
     */
    protected function refreshTestDatabase()
    {
        if (! RefreshDatabaseState::$migrated) {
            foreach ($this->connectionsToTransact() as $database) {
                $this->artisan('db:wipe', array_filter([
                    '--database' => $database,
                    '--drop-views' => $this->shouldDropViews(),
                    '--drop-types' => $this->shouldDropTypes(),
                    '--force' => true,
                ]));
            }

            $this->artisan('migrate', [
                '--force' => true,
            ]);

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

        $this->beginDatabaseTransaction();
    }
}
```


[^1]: https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Testing/RefreshDatabase.php#L45-L64
[^2]: https://github.com/laravel/framework/blob/6.x/src/Illuminate/Database/Console/Migrations/FreshCommand.php#L40-L53
[^3]: https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Testing/RefreshDatabase.php#L75-L94
[^4]: https://github.com/laravel/framework/blob/6.x/src/Illuminate/Foundation/Testing/DatabaseTransactions.php#L30-L39