LaravelのHTTPテストでリクエストを複数回投げる方法

# 1. はじめに
- 久しぶりの投稿になりました。YUZURIHAの松村です。
- 昨年はPythonの記事ばかり投稿してましたが、ここ半年以上はずっとPHPのLaravelをやってます。
- 先日LaravelでHTTPリクエストのテストを書こうと思い、公式ドキュメントを見てるとこんな注意書きを見つけました。
  - https://laravel.com/docs/10.x/http-tests#making-requests
    ```
    In general, each of your tests should only make one request to your application.
    Unexpected behavior may occur if multiple requests are executed within a single test method.
    ```
  - 「単一のテストメソッド内で複数のリクエストが実行されると、予期しない動作が発生する可能性がある」
  - 要するに、テストメソッド1つにつきHTTPリクエストは1つしか投げてはいけないらしいです。
- この制約に基づいて書くにはどうすれば良いのか、LaravelがテストのためにサポートしているPHPUnitについて調べてみました。

# 2. テストメソッドの実行順を指定
- 例えば「ユーザ名でユーザを検索して、その検索結果を元にファイルをダウンロードする」というテストを行う場合、以下のように書いてしまうと1つのメソッド内でHTTPリクエストを2回投げることになります。
  ```php
  /**
   * @return void
   */
  public function testSearchAndDownloadSuccess(): void
  {
    $searchResponse = $this->get('/search', [
        'name' => '太郎'
    ]);
    $searchResponse->assertStattus(200);

    $userId = $searchResponse->getSession()->get('user_id');

    $downloadResponse = $this->get('/download', [
        'user_id' => $userId
    ]);
    $downloadResponse->assertDownload();
  }
  ```
- こういう時は、PHPUnitの`depends`という機能を使います。
- `depends`を使って書くとこうなります。
  ```php
  /**
   * @return integer
   */
  public function testSearchSuccess(): integer
  {
    $searchResponse = $this->get('/search', [
        'name' => '太郎'
    ]);
    $searchResponse->assertStattus(200);

    $userId = $searchResponse->getSession()->get('user_id');
    return $userId;
  }

  /**
   * @depends testSearchSuccess
   * @param integer $userId
   * @return void
   */
  public function testDownloadSuccess($userId): void
  {
    $downloadResponse = $this->get('/download', [
        'user_id' => $userId
    ]);
    $downloadResponse->assertDownload();
  }
  ```
- こうすると、アノテーション`@depends`に指定した`testSearchSuccess`を先に実行してから`testDownloadSuccess`を実行します。
- 返り値を引数として受け渡すことができるので、`testSearchSuccess`で取得した`$userId`は`testDownloadSuccess`に渡されます。
- これにより「テストメソッド1つにつきHTTPリクエストは1つ」という制約を守りながら、「検索してからファイルをダウンロードする」というテストを実行することができます。

# 3. 同じテストメソッドを複数のパラメータで実行
- 同じテストケースを複数のパラメータで実行したい時、`foreach`を使って以下のように書いてしまうと、1つのメソッド内でHTTPリクエストを何回も投げることになります。
  ```php
  /**
   * @return void
   */
  public function testRequestSuccess(): void
  {
      $paramList = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
      foreach ($paramList as $param) {
          $response = $this->post('/hoge', [
              'param' => $param
          ]);
          $response->assertStatus(200);
      }
  }
  ```
- こういう時は、PHPUnitの`dataProvider`という機能を使います。
- `dataProvider`を使って書くとこうなります。
  ```php
  /**
   * @return array
   */
  public function paramList(): array
  {
      return [
          ['1'], ['2'], ['3'], ['4'], ['5'], ['6'], ['7'], ['8'], ['9']
      ];
  }

  /**
   * @dataProvider paramList
   * @param string $param
   * @return void
   */
  public function testRequestSuccess($param): void
  {
      $response = $this->post('/hoge', [
          'param' => $param
      ]);
      $response->assertStatus(200);
  }
  ```
- これで、パラメータ1つにつきメソッドを1回ずつ実行するようになります。
  ```bash
  % php artisan test --filter 'Tests\\Feature\\RequestTest'
  ~
     PASS  Tests\Feature\Request
    ✓ request success
    ✓ request success with data set #1
    ✓ request success with data set #2
    ✓ request success with data set #3
    ✓ request success with data set #4
    ✓ request success with data set #5
    ✓ request success with data set #6
    ✓ request success with data set #7
    ✓ request success with data set #8

    Tests:  9 passed
    Time:   32.34s
  ```


- 注意点としては、パラメータの配列を2次元配列で書くことです。
  - 2次元配列の親配列はテストケースが要素になり、子配列はテストケース内のパラメータが要素になります。
  - 今回は1つのテストケースに1つのパラメータしかないため、子配列には要素1つになっています。
  - 以下のように1次元配列で書いてしまうと、1つのテストケースとして見做されてテストは1回しか実行されないので注意が必要です。
    ```php
    /**
     * @return array
     */
    public function paramList(): array
    {
        return ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
    }
    ```
    ```bash
    % php artisan test --filter 'Tests\\Feature\\RequestTest'
    ~
       PASS  Tests\Feature\Request
      ✓ request success

      Tests:  9 passed
      Time:   3.48s
    ```

# 4. 最後に
- 最初に「テストメソッド1つにつき、HTTPリクエストは1つしか投げてはいけない」という注意書きを公式ドキュメントで見つけた時は、「この制約はちょっと面倒だな」と思いました。
- しかし、LaravelがサポートしているPHPUnitについて調べると、しっかり回避方法は用意されていました。
- Frameworkを使う時はそのFrameworkの公式ドキュメントをちゃんと読んだりするのはもちろん大切ですが、サポートしているライブラリについてもしっかり調べると、そのFramework使いこなせるようになるなと実感しました。