カテゴリー
コンピューター

Laravel の `DATABASE_URL` を外部の環境変数 (server レベル) で設定している場合、 PHPUnit テストの 1 個目は通常に行われるのに 2 個目で DB 接続エラーとなってしまう問題を解決した

エラー対処まとめ

  • 今回の状況
    • Laravel 8.55.0
    • PHPUnit テストをインメモリ SQLite で行いたい。
    • DATABASE_URL を外部の環境変数 (server レベル) で設定している。
  • テスト用に DATABASE_URL を指定する以外に、 DB_CONNECTIONDB_DATABASE も指定する必要がある。
  • 次のように phpunit.xml に指定すればよい。
        <server name="DATABASE_URL" value="sqlite:///:memory:?foreign_key_constraints=true" force="true"/>
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>

エラーの内容

Laravel 6 ユニットテストをする際 Docker Compose の環境変数を上書きできずにはまったが phpunit.xml ドキュメントを読んで解決した – oki2a24 の設定、つまり、 phpunit.xmlDATABASE_URL を設定しました。

以下はその時の phpunit.xml の抜粋となります。

        <server name="DATABASE_URL" value="sqlite:///:memory:?foreign_key_constraints=true" force="true"/>

DB を使用するユニットテストの一つ目を作り、問題なく動作しました。そして、 DB を使用するユニットテストをもう一つ作って実行した時のことです。次のエラーとなってしまいました。

1 つめのテストは正常に実行されましたけれども、 2 つ目でエラーが発生しています。データベース関係で、接続周りのエラーのようです。両方とも成功する、または、失敗する、ならばまだわかるのですけれども、片方のみ失敗する、というのは根が深そうに感じます。

app@33f1a41f3128:/var/www/html/laravel$ php artisan test tests/Feature/Controllers/AuthController/LoginTest.php 

   FAIL  Tests\Feature\Controllers\AuthController\LoginTest
  ✓ ログイン成功すること
  ⨯ ログイン失敗すること

  ---

  • Tests\Feature\Controllers\AuthController\LoginTest > ログイン失敗すること
   Illuminate\Database\QueryException 

  SQLSTATE[HY000]: General error: 1 no such table: users (SQL: insert into "users" ("name", "email", "email_verified_at", "password", "remember_token", "updated_at", "created_at") values (中津川 知実, sayuri60@example.net, 2021-09-13 06:50:28, $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi, Wh9O3I9sQu, 2021-09-13 06:50:28, 2021-09-13 06:50:28))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:692
    688▕         // If an exception occurs when attempting to run a query, we'll format the error
    689▕         // message to include the bindings with SQL, which will make this exception a
    690▕         // lot more helpful to the developer instead of just the database's errors.
    691▕         catch (Exception $e) {
  ➜ 692▕             throw new QueryException(
    693▕                 $query, $this->prepareBindings($bindings), $e
    694▕             );
    695▕         }
    696▕     }

      +16 vendor frames 
  17  tests/Feature/Controllers/AuthController/LoginTest.php:53
      Illuminate\Database\Eloquent\Factories\Factory::create()


  Tests:  1 failed, 1 passed
  Time:   0.80s

app@33f1a41f3128:/var/www/html/laravel$ 

エラーの調査

調査するために、データベース接続を次のようなデバッグログをテストメソッド冒頭に入れることで調べました。これは、 Laravel 6 で今データベース接続がどうなっているか簡単に確認する方法 – oki2a24 を元にしています。

logger('ログイン成功すること');
logger(print_r(\DB::connection()->getConfig(), true));
logger(print_r(\DB::connection()->getPdo(), true));

そして再度テストを実行したときの結果がこちらです。

内容を見てみると、インメモリ SQLite を使っている一方で、デフォルトデータベース名は [name] => pgsql と PostgreSQL となっています。また、ユニットテストの1個目と2個目で同じ内容、となりました。

app@33f1a41f3128:/var/www/html/laravel$ php artisan test tests/Feature/Controllers/AuthController/LoginTest.php 
[2021-09-13 07:00:14] testing.DEBUG: ログイン成功すること  
[2021-09-13 07:00:14] testing.DEBUG: Array
(
    [driver] => sqlite
    [host] => 127.0.0.1
    [port] => 5432
    [database] => :memory:
    [username] => root
    [password] => 
    [charset] => utf8
    [prefix] => 
    [prefix_indexes] => 1
    [schema] => public
    [sslmode] => prefer
    [foreign_key_constraints] => 1
    [name] => pgsql
)
  
[2021-09-13 07:00:14] testing.DEBUG: PDO Object
(
)
  
[2021-09-13 07:00:15] testing.DEBUG: ログイン失敗すること  
[2021-09-13 07:00:15] testing.DEBUG: Array
(
    [driver] => sqlite
    [host] => 127.0.0.1
    [port] => 5432
    [database] => :memory:
    [username] => root
    [password] => 
    [charset] => utf8
    [prefix] => 
    [prefix_indexes] => 1
    [schema] => public
    [sslmode] => prefer
    [foreign_key_constraints] => 1
    [name] => pgsql
)
  
[2021-09-13 07:00:15] testing.DEBUG: PDO Object
(
)
  

   FAIL  Tests\Feature\Controllers\AuthController\LoginTest
  ✓ ログイン成功すること
  ⨯ ログイン失敗すること

  ---

  • Tests\Feature\Controllers\AuthController\LoginTest > ログイン失敗すること
   Illuminate\Database\QueryException 

  SQLSTATE[HY000]: General error: 1 no such table: users (SQL: insert into "users" ("name", "email", "email_verified_at", "password", "remember_token", "updated_at", "created_at") values (宇野 零, hiroshi18@example.com, 2021-09-13 07:00:15, $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi, jpuqcof3i2, 2021-09-13 07:00:15, 2021-09-13 07:00:15))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:692
    688▕         // If an exception occurs when attempting to run a query, we'll format the error
    689▕         // message to include the bindings with SQL, which will make this exception a
    690▕         // lot more helpful to the developer instead of just the database's errors.
    691▕         catch (Exception $e) {
  ➜ 692▕             throw new QueryException(
    693▕                 $query, $this->prepareBindings($bindings), $e
    694▕             );
    695▕         }
    696▕     }

      +16 vendor frames 
  17  tests/Feature/Controllers/AuthController/LoginTest.php:53
      Illuminate\Database\Eloquent\Factories\Factory::create()


  Tests:  1 failed, 1 passed
  Time:   0.91s

app@33f1a41f3128:/var/www/html/laravel$ 

ではどうすれば良いでしょうか?いろいろ試して、 デフォルトデータベース名を [name] => pgsql ではなく、 [name] => sqlite としてみました。

phpunit.xml

        <server name="DATABASE_URL" value="sqlite:///:memory:?foreign_key_constraints=true" force="true"/>
        <server name="DB_CONNECTION" value="sqlite"/>

その時の結果がこちらです。

やっぱりダメでした。ですけれども、 [name] => sqlite と変更はできました。

app@33f1a41f3128:/var/www/html/laravel$ php artisan test tests/Feature/Controllers/AuthController/LoginTest.php 
[2021-09-14 06:39:28] testing.DEBUG: ログイン成功すること  
[2021-09-14 06:39:28] testing.DEBUG: Array
(
    [driver] => sqlite
    [database] => :memory:
    [prefix] => 
    [foreign_key_constraints] => 1
    [name] => sqlite
)
  
[2021-09-14 06:39:28] testing.DEBUG: PDO Object
(
)
  
[2021-09-14 06:39:28] testing.DEBUG: ログイン失敗すること  
[2021-09-14 06:39:28] testing.DEBUG: Array
(
    [driver] => sqlite
    [database] => :memory:
    [prefix] => 
    [foreign_key_constraints] => 1
    [name] => sqlite
)
  
[2021-09-14 06:39:28] testing.DEBUG: PDO Object
(
)
  

   FAIL  Tests\Feature\Controllers\AuthController\LoginTest
  ✓ ログイン成功すること
  ⨯ ログイン失敗すること

  ---

  • Tests\Feature\Controllers\AuthController\LoginTest > ログイン失敗すること
   Illuminate\Database\QueryException 

  SQLSTATE[HY000]: General error: 1 no such table: users (SQL: insert into "users" ("name", "email", "email_verified_at", "password", "remember_token", "updated_at", "created_at") values (小泉 加奈, kazuya.sakamoto@example.net, 2021-09-14 06:39:28, $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi, TgzwpVY7Jg, 2021-09-14 06:39:28, 2021-09-14 06:39:28))

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:692
    688▕         // If an exception occurs when attempting to run a query, we'll format the error
    689▕         // message to include the bindings with SQL, which will make this exception a
    690▕         // lot more helpful to the developer instead of just the database's errors.
    691▕         catch (Exception $e) {
  ➜ 692▕             throw new QueryException(
    693▕                 $query, $this->prepareBindings($bindings), $e
    694▕             );
    695▕         }
    696▕     }

      +16 vendor frames 
  17  tests/Feature/Controllers/AuthController/LoginTest.php:53
      Illuminate\Database\Eloquent\Factories\Factory::create()


  Tests:  1 failed, 1 passed
  Time:   0.90s

app@33f1a41f3128:/var/www/html/laravel$ 

わかりません。。。さらにいろいろ調べてたどり着いたのは、そういえば元々の phpunit.xml では DB_CONNECTIONDB_DATABASE がコメントではあるものの記述されていることに注目しました。

そこで次のようにしてみますと、、、

phpunit.xml

        <server name="DATABASE_URL" value="sqlite:///:memory:?foreign_key_constraints=true" force="true"/>
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>

なんとエラーが消え、当初行いたかった普通にユニットテスを実行するが普通にできました!

app@33f1a41f3128:/var/www/html/laravel$ php artisan test tests/Feature/Controllers/AuthController/LoginTest.php 
[2021-09-14 06:54:36] testing.DEBUG: ログイン成功すること  
[2021-09-14 06:54:36] testing.DEBUG: Array
(
    [driver] => sqlite
    [database] => :memory:
    [prefix] => 
    [foreign_key_constraints] => 1
    [name] => sqlite
)
  
[2021-09-14 06:54:36] testing.DEBUG: PDO Object
(
)
  
[2021-09-14 06:54:37] testing.DEBUG: ログイン失敗すること  
[2021-09-14 06:54:37] testing.DEBUG: Array
(
    [driver] => sqlite
    [database] => :memory:
    [prefix] => 
    [foreign_key_constraints] => 1
    [name] => sqlite
)
  
[2021-09-14 06:54:37] testing.DEBUG: PDO Object
(
)
  

   PASS  Tests\Feature\Controllers\AuthController\LoginTest
  ✓ ログイン成功すること
  ✓ ログイン失敗すること

  Tests:  2 passed
  Time:   0.89s

app@33f1a41f3128:/var/www/html/laravel$ 

データベース接続設定の内容も、全て SQLite となっており、これで良さそうです!

補足。むしろ DATABASE_URL いらないのでは? と試した結果

次の結果となりました。[name] => sqlite とはなっていますけれども、内容から判断するにどうみてもインメモリ SQLite ではなく PostgreSQL につながっています。

しかも、データベースを確認してみると、 PostgreSQL にレコードが保存されてしまっていました。

つまり、私の DATABASE_URL を外部の環境変数 (server レベル) で設定している環境では、 DATABASE_URL もテスト用にしっかりと上書きする必要がある、といえます。

app@33f1a41f3128:/var/www/html/laravel$ php artisan test tests/Feature/Controllers/AuthController/LoginTest.php 
[2021-09-14 06:55:28] testing.DEBUG: ログイン成功すること  
[2021-09-14 06:55:28] testing.DEBUG: Array
(
    [driver] => pgsql
    [database] => laravel
    [prefix] => 
    [foreign_key_constraints] => 1
    [host] => db
    [port] => 5432
    [username] => laravel
    [password] => secret
    [charset] => utf8
    [prefix_indexes] => 1
    [schema] => public
    [sslmode] => prefer
    [name] => sqlite
)
  
[2021-09-14 06:55:28] testing.DEBUG: PDO Object
(
)
  
[2021-09-14 06:55:29] testing.DEBUG: ログイン失敗すること  
[2021-09-14 06:55:29] testing.DEBUG: Array
(
    [driver] => pgsql
    [database] => laravel
    [prefix] => 
    [foreign_key_constraints] => 1
    [host] => db
    [port] => 5432
    [username] => laravel
    [password] => secret
    [charset] => utf8
    [prefix_indexes] => 1
    [schema] => public
    [sslmode] => prefer
    [name] => sqlite
)
  
[2021-09-14 06:55:29] testing.DEBUG: PDO Object
(
)
  

   PASS  Tests\Feature\Controllers\AuthController\LoginTest
  ✓ ログイン成功すること
  ✓ ログイン失敗すること

  Tests:  2 passed
  Time:   1.15s

app@33f1a41f3128:/var/www/html/laravel$ 

おわりに

DATABASE_URL ではデータベースも指定するので、デフォルトデータベース名に関係なく設定が可能と思っておりました。しかし、少なくともユニットテストではそうではないようです。

ユニットテスト以外では、本投稿のような対処は不要ですので、なんだかこれは Laravel 自体のバグのようにも思えます。ですので、バージョンアップしたときに本投稿の現象は発生しなくなるかもしれません。

以上です。

コメントを残す