はじめに
めちゃくちゃはまったので記事にします。
問題
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で上書きされるので、実質なんでも良い
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();
};
~~~中略~~~
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; ;
}
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);
}
// 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');
console.log('record_delete.ts--------のテストのモック通過');
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>
);
// 確認用出力
// screen.debug();
await waitFor(() => {
const dialogTitle = screen.getByText('登録');
expect(dialogTitle).toBeInTheDocument();
});
// 確認用出力
// screen.debug();
const deleteButton = await waitFor(() =>
screen.getByTestId('delete-button-5')
);
// console.log("deleteButton", deleteButton)
await waitFor(() => {
fireEvent.click(deleteButton);
});
// 確認用出力
// screen.debug();
await waitFor(() => {
// expect(screen.queryByText('Testtest5 5時間')).toBeInTheDocument();
expect(screen.queryByText('Testtest5 5時間')).not.toBeInTheDocument();
});
});
});
おわりに
分からないことが多すぎると、思考停止になってしまいます。
自分の理解が浅いとChatGPTへの質問も浅くなってしまい、良い回答が得られなかったです。
角度を変えながらエラーの原因を考え、1つ1つ問題をつぶしていけるようにしたいです。