🎻

Symfonyでログイン後の画面を機能テストする方法【簡単です】

2020/03/24に公開

Symfonyでログインが必要なページの機能テストを行う方法を書いておきます。めっちゃ簡単です。

1. 普通の機能テスト

まず、ログインが不要なページの機能テストとして以下のようなコードがあるとします。

class FooControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = static::createClient();
        $client->request('GET', '/foo/');
        $this->assertEquals(200, $client->getResponse()->getStatusCode());
    }
}

2. ログインに使用するユーザーのフィクスチャを作成

テスト時にログインをシミュレートするために、ログインに使用するユーザーがデータベースに登録されている必要があるので、yamlフィクスチャなどを使ってテスト用データベースにユーザーを作成します。

Symfonyで機能テスト時にyamlフィクスチャを使う方法については こちらの記事 で詳しく説明していますので、ご参照ください。

データベースに登録するユーザーのパスワードは平文ではなくエンコードしたものにしなければいけないので、以下のようにSecurityバンドルのコマンドでエンコード後の文字列を用意します。

今回は password というパスワード文字列をエンコードします。

$ bin/console security:encode-password password

------------------ ------------------------------------------------------------------
 Key                Value
------------------ ------------------------------------------------------------------
 Encoder used       Symfony\Component\Security\Core\Encoder\MigratingPasswordEncoder
 Encoded password   $2y$13$pX/zEqXb.XqbF30gXTCuJuy/dWIobEOyINTqT7hnzCfZl.ewNxrg2
------------------ ------------------------------------------------------------------

この $2y$13$pX/zEqXb.XqbF30gXTCuJuy/dWIobEOyINTqT7hnzCfZl.ewNxrg2 をユーザーのパスワードとしてデータベースに保存しておけば、 password というパスワードでログインができます。

というわけで、以下のようなフィクスチャを書けばよいでしょう。

App\Entity\User:
  user:
    username: user
    password: \$2y\$13\$pX/zEqXb.XqbF30gXTCuJuy/dWIobEOyINTqT7hnzCfZl.ewNxrg2

$\$ に置き換えている点に注意してください。

yamlの仕様 の中に明確な言及を見つけられなかったのですが、どうも $ はyamlではエスケープしないと文字列として扱えないようです。

3. テスト環境のみ認証方式をBASIC認証にする

こちらの公式ドキュメント にあるように、機能テストにおいてユーザーログインをシミュレートするには、テスト環境のみBASIC認証に変えてしまうのが手っ取り早いです。

そのためには、ドキュメントのとおり、以下の内容で config/packages/test/security.yaml を作ってしまえばOKです。

security:
    firewalls:
        main:
            http_basic: ~

もし config/packages/security.yamlmain でないファイアウォール名を使っている場合は、 main の部分をその名前に書き換えてください。

4. 機能テストのコードを修正する

以上ですべての準備が整ったので、機能テストのコードを修正しましょう。

  class FooControllerTest extends WebTestCase
  {
+     use FixturesTrait;
+ 
+     protected function setUp(): void
+     {
+         // フィクスチャからデータベースを作成
+         $this->loadFixtureFiles([
+             __DIR__ . '/../../tests/fixtures/user.yaml',
+         ]);
+     }
+ 
      public function testIndex()
      {
+         // 未ログインでは302で(ログインページに)リダイレクトされる
+         $client = static::createClient();
+         $client->request('GET', '/foo/');
+         $this->assertEquals(302, $client->getResponse()->getStatusCode());
+ 
-         $client = static::createClient();
+         // ログイン状態のクライアントを使う
+         $client = $this->createAuthorizedClient();
          $client->request('GET', '/foo/');
          $this->assertEquals(200, $client->getResponse()->getStatusCode());
      }
+ 
+     // BASIC認証済みのクライアントを作成
+     private function createAuthorizedClient(): KernelBrowser
+     {
+         return static::createClient([], [
+             'PHP_AUTH_USER' => 'user',
+             'PHP_AUTH_PW' => 'password',
+         ]);
+     }
  }

たくさん追記したように見えますが、

  • yamlフィクスチャからデータベースを作成
  • 未ログイン状態のテストを追加
  • BASIC認証済みのクライアントを作成する処理を追加

しているだけです👍

PHP_AUTH_USER PHP_AUTH_PW については下記のPHPマニュアルをご参照ください。

https://www.php.net/manual/ja/features.http-auth.php

5. Symfony 4.4以降でのdeprecation notice対策

Symfony 4.4以上を使っている場合、ここまでに書いたテストを実行すると以下のようなdeprecation noticeが出ます。

1x: Calling "Symfony\Bundle\FrameworkBundle\Test\WebTestCase::createClient()" while a kernel has been booted is deprecated since Symfony 4.4 and will throw in 5.0, ensure the kernel is shut down before calling the method.
  1x in FooControllerTest::testIndex from App\Controller

エラーメッセージ を読むと、カーネルが起動している状態でさらに別のカーネルを起動させる行為はSymfony 4.4以降で非推奨となっているとのことです。

今回のように未ログインのクライアントとログイン済みのクライアントを並行して作成しようとするとこの非推奨に抵触することになります。

こちらの記事 に詳しく書いていますが self::createClient() が実行される前に常に self::ensureKernelShutdown() を実行するようにしておけばよいので、以下のようにして createClient() メソッドをオーバーライドしておけばdeprecation notionceは解消できます👍

+ protected static function createClient(array $options = [], array $server = []): KernelBrowser
+ {
+     self::ensureKernelShutdown();
+ 
+     return parent::createClient($options, $server);
+ }
+ 
  private function createAuthorizedClient(): KernelBrowser
  {
      return static::createClient([], [
          'PHP_AUTH_USER' => 'user',
          'PHP_AUTH_PW' => 'password',
      ]);
  }

まとめ

  • Symfonyでログインが必要なページを機能テストする場合、yamlフィクスチャでユーザーを作る&テスト環境のみBASIC認証にする、で簡単に対応可能
  • Symfony 4.4以降はテスト中に複数カーネル起動するとdeprecation noticeになるので要注意
GitHubで編集を提案

Discussion