Jestでmockを作って、データを削除するテストをする(supabase、React.js、TypeScript)


# 問題
Jestでmockを作って、データを削除するテストがうまくいかなかった。

そもそもやりたいことが曖昧だった。
・mockの書き方がわかっていなかった
・何をやりたいのかがわかっていなかった
(どの関数をモック化して、何のデータを得たい(return)したいのか)

# 解決方法
@Sicut_studyさんにアドバイスいただきながら、以下のように対応した。

- App.tsxで、APIコールする部分は、別ファイルに書き出す
(データ取得するための関数(GetAllRecords)、削除するための関数(RecordDelete)それぞれ)  

- AppComponent.spec.tsx(testファイル)で、それぞれの関数をmock化する
(GetAllRecordsするためのmock、RecordDeleteするためのmock)  

- 削除するためのモック(RecordDelete)を表現する方法
実際にapiをコールしてdeleteメソッドを使う必要はない
App.tsxのhandleDeleteメソッドの中で、fetchData()があり、再度GetAllRecordsが呼ばれるので、1回目に叩かれる時、2回目に叩かれる時で、データを変更する(1回目「id5, id10」、2回目「id10」のみ、など)
RecordDeleteのmockは、動いてもGetAllRecordsで上書きされるので、実質なんでも良い


```src/App.tsx
import { GetAllRecords } from './lib/record';
import { RecordDelete } from './lib/record_delete';

// 略
  const fetchData = async () => {
    const getAllRecordMethod = async () => {
      // GetAllRecords();を外部ファイルに書く
      const todoRecord = await GetAllRecords();
      setData(todoRecord);
      setIsLoading(false);
    };
    getAllRecordMethod();
  };

// 略
```

```record.tsx
import { Record } from "../domain/record";
import supabase from "../utils/supabase";

export async function GetAllRecords(): Promise<Record[]> {
    const response = await supabase.from("study-record").select("*");
    if (response.error) {
        throw new Error(response.error.message);
    }

    const data = response.data.map((todo) => {
        return Record.newRecord(todo.id, todo.title, todo.time);
    });
    return data; ;
}
```

```record_delete.ts
import { Record } from '../domain/record';
import supabase from '../utils/supabase';

export async function RecordDelete(id: string): Promise<void> {
  await supabase.from('study-record').delete().eq('id', id);
}
```

```AppComponent.spec.tsx
// testファイル

// mockを使ったテストは、describeを使わないと、global的な扱いとなり、他のテストに影響する
describe('mockを使ったテスト', () => {

// '@/lib/record.ts'のGetAllRecordsをモック化
jest.mock('@/lib/record.ts', () => {
  const { Record } = jest.requireActual('@/domain/record');
  return {
// GetAllRecordsをモック化
    GetAllRecords: jest
      .fn()
// 1回目に呼び出されたときに返すデータ
      .mockImplementationOnce(() =>
        Promise.resolve([
          new Record('5', 'Testtest5', 5),
          new Record('10', 'Testtest10', 10),
        ])
      )
      .mockImplementationOnce(() =>
        Promise.resolve([
// 2回目に呼び出されたときに返すデータ
          // new Record('5', 'Testtest5', 5),
          new Record('10', 'Testtest10', 10),
        ])
      ),
  };


// '@/lib/record_delete.ts'のRecordDeleteをモック化
jest.mock('@/lib/record_delete.ts', () => {
        const { Record } = jest.requireActual('@/domain/record');
  return {
// RecordDeleteをモック化
        RecordDelete:  jest
    .fn()
    .mockImplementationOnce(() =>
      Promise.resolve([
// App.tsxではRecordDeleteの後、再度GetAllRecordsが呼ばれて上書きされるので、実質何でも良い
        // new Record('5', 'Testtest5', 5),
        new Record('10', 'Testtest10', 10),
      ])
    ),
  };
});


  test('削除ができること', async () => {
    render(
      <ChakraProvider value={defaultSystem}>
        <App />
      </ChakraProvider>
    );
    await waitFor(() => {
      const dialogTitle = screen.getByText('登録');
      expect(dialogTitle).toBeInTheDocument();
    });

    const deleteButton = await waitFor(() =>
      screen.getByTestId('delete-button-5')
    );

    await waitFor(() => {
      fireEvent.click(deleteButton);
    });

    await waitFor(() => {
      expect(screen.queryByText('Testtest5 5時間')).not.toBeInTheDocument();
    });
  });
});
```

# おわりに
分からないことが多すぎると、思考停止になってしまいます。
自分の理解が浅いとChatGPTへの質問も浅くなってしまい、良い回答が得られませんでした。
角度を変えながらエラーの原因を考え、1つ1つ問題をつぶしていけるようにしたいです。