エラー対処まとめ
- 今回の状況
- Laravel 8.55.0
- PHPUnit テストをインメモリ SQLite で行いたい。
DATABASE_URL
を外部の環境変数 (server レベル) で設定している。
- テスト用に
DATABASE_URL
を指定する以外に、DB_CONNECTION
とDB_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.xml
に DATABASE_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_CONNECTION
と DB_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 自体のバグのようにも思えます。ですので、バージョンアップしたときに本投稿の現象は発生しなくなるかもしれません。
以上です。