【Jest】モック化した関数が複数回呼ばれる際にレスポンスを変えたい

# はじめに
モック化した関数が複数回呼ばれる際に返却される値を変えるやり方について書いていきます。モック化については以前も簡単に記事を書きました。

https://qiita.com/bisketoriba/items/8e2295895d74d646b1e6

# 今回実現したいこと
- 学習記録アプリで削除ボタンが押された際に要素が1つ減ることをテストしたい
- 関数はモック化しており初期表示ではデータが3つのレコード、削除ボタン押下後はデータが2つのレコードを返すようにする

# 実装してみる
まずはすべての学習記録を取得する関数(getAllRecords)と1つの学習記録を削除する関数(deleteRecord)をモック化していきます。ちなみにバックエンド側はsupabaseを利用しています。
**mockResolvedValueOnce**で一度だけ指定したデータを呼び出すようにしているので、2回目のgetAllRecordsからは2つデータが呼ばれるようになっています。
```js  
jest.mock("../lib/supabasefunctions", () => {
  return {
    getAllRecords: jest
    .fn()
    // 初期表示のデータ
    .mockResolvedValueOnce([
     { id: 1, title: "react", time: "10" },
     { id: 2, title: "git", time: "20" },
     { id: 3, title: "linux", time: "30" },
   ])
    // 削除後のデータ
    .mockResolvedValue([
     { id: 1, title: "react", time: "10" },
     { id: 2, title: "git", time: "20" },
   ]),
    deleteRecord: jest.fn(),
  };
});
```
これでモック化はできているので、実際に削除のテストを書いていきます。
```js
describe("学習記録アプリのテスト", () => {
 test("削除ボタンを押したらレコードが1つ減ること", async () => {
    // 1回目のgetAllRecords
    await waitFor(() => {
      const recordList = screen.getByTestId("record-list");
      expect(recordList).toBeInTheDocument();
      const rows = recordList.querySelectorAll("tr");
      expect(rows.length - 1).toBe(3);
    });
    // 削除ボタンをクリックして削除処理を実行
    const deleteButtons = screen.getAllByTestId("delete-button");
    fireEvent.click(deleteButtons[0]);
    // 2回目のgetAllRecords
    await waitFor(() => {
      const recordList = screen.getByTestId("record-list");
      expect(recordList).toBeInTheDocument();
      const rows = recordList.querySelectorAll("tr");
      expect(rows.length - 1).toBe(2);
    });
  });
});
```
これでモック化した関数が2回目に呼ばれる際の挙動を変えることができました。注意点として削除や追加,編集などのテストを同一ファイルでやろうとするとモックデータの呼ばれる順序に依存してしまうことがあるので、その場合は別ファイルでテストを書いた方が良いです。



<details><summary>テストコード全体</summary>

```js
import App from "../App";
import {render,screen,waitFor,fireEvent,} from "@testing-library/react";

jest.mock("../lib/supabasefunctions", () => {
  return {
    getAllRecords: jest
    .fn()
    // 初期表示のデータ
    .mockResolvedValueOnce([
     { id: 1, title: "react", time: "10" },
     { id: 2, title: "git", time: "20" },
     { id: 3, title: "linux", time: "30" },
   ])
    // 削除後のデータ
    .mockResolvedValue([
     { id: 1, title: "react", time: "10" },
     { id: 2, title: "git", time: "20" },
   ]),
    deleteRecord: jest.fn(),
  };
});

describe("学習記録アプリのテスト", () => {
 test("削除ボタンを押したらレコードが1つ減ること", async () => {
    // 1回目のgetAllRecords
    await waitFor(() => {
      const recordList = screen.getByTestId("record-list");
      expect(recordList).toBeInTheDocument();
      const rows = recordList.querySelectorAll("tr");
      expect(rows.length - 1).toBe(3);
    });
    // 削除ボタンをクリックして削除処理を実行
    const deleteButtons = screen.getAllByTestId("delete-button");
    fireEvent.click(deleteButtons[0]);
    // 2回目のgetAllRecords
    await waitFor(() => {
      const recordList = screen.getByTestId("record-list");
      expect(recordList).toBeInTheDocument();
      const rows = recordList.querySelectorAll("tr");
      expect(rows.length - 1).toBe(2);
    });
  });
});
```

</details>

# おわりに
モックを利用したテストはかなり苦労しました。ただテストは毎回似たようなことを書くので慣れればスラスラできるようになりそうだと思いました。

# 参考
https://jestjs.io/ja/docs/mock-function-api#mockfnmockresolvedvalueoncevalue

https://blog.nightonly.com/2022/07/30/jest%E3%81%A7%E5%90%8C%E3%81%98mock%E3%81%8C%E8%A4%87%E6%95%B0%E5%9B%9E%E5%8F%A9%E3%81%8B%E3%82%8C%E3%82%8B%E3%83%86%E3%82%B9%E3%83%88%E3%82%92%E6%9B%B8%E3%81%8F/