はじめに
ローカルで通ったテストがデプロイ環境では通らない、ということはよくあるようで、検索するとQiita記事もよく出てきます。しかし、その逆はあまりないようで、検索してもあまりHitしません。
今回、この記事を書くにあたってそのエラーに遭遇して解決に苦慮したため、備忘録を兼ねて記事にします。
事象
- デプロイした学習記録アプリの
index.html
のtitleを変更したところ、一度パスしたはずの記録追加テストがローカル環境下で通らなくなっていた - エラーメッセージは下記の通り
TestingLibraryElementError: Unable to find an element by: [data-testid="log"]
-
index.html
の変更を元に戻しても解消せず、エラー内容的にも関係なさそう
該当コード
addRecord.test.jsx
import '@testing-library/jest-dom';
import { screen, fireEvent, render, waitFor } from '@testing-library/react';
import App from '../App';
import { TotalTimeProvider } from '../Providers/TotalTimeProvider';
import { addRecord, getAllLogs } from '../utils/supabaseFunctions';
jest.setTimeout(20000);
const mockLogs = [{ id: 1, title: 'test', time: 1 }];
jest.mock('../utils/supabaseFunctions', () => ({
getAllLogs: jest.fn(() => Promise.resolve(mockLogs)),
addRecord: jest.fn((title, time) => {
const newLog = { id: mockLogs.length + 1, title, time };
mockLogs.push(newLog);
return Promise.resolve(newLog);
}),
}));
describe('動作テスト', () => {
it('記録追加', async () => {
render(
<TotalTimeProvider>
<App />
</TotalTimeProvider>
);
await waitFor(
() => {
expect(screen.getByTestId('log')).toHaveTextContent('test 1時間');
},
{ timeout: 5000 }
);
fireEvent.change(screen.getByLabelText('学習内容'), {
target: { value: 'test2' },
});
fireEvent.change(screen.getByLabelText('学習時間'), {
target: { value: '2' },
});
fireEvent.click(screen.getByText('登録'));
await waitFor(
() => {
const logs = screen.getAllByTestId('log');
expect(logs).toHaveLength(2);
},
{ timeout: 5000 }
);
const logs = screen.getAllByTestId('log');
expect(logs[1]).toHaveTextContent('test2 2時間');
expect(addRecord).toHaveBeenCalledWith('test2', 2);
expect(getAllLogs).toHaveBeenCalledTimes(2);
});
});
解決策
- Loading画面が消えるまで次の処理を待つようにした
-
render
とfireEvent
をact
で囲んだ
addRecord.test.jsx
import {
screen,
fireEvent,
render,
waitFor,
act,
} from '@testing-library/react';
(中略)
await act(async () => {
render(
<TotalTimeProvider>
<App />
</TotalTimeProvider>
);
});
await waitFor(
() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
},
{ timeout: 5000 }
);
(中略)
await act(async () => {
fireEvent.change(screen.getByLabelText('学習内容'), {
target: { value: 'test2' },
});
fireEvent.change(screen.getByLabelText('学習時間'), {
target: { value: '2' },
});
fireEvent.click(screen.getByText('登録'));
});
(以下略)
エラーは解消したが……
-
render
とかfireEvent
ってact
で囲む必要ないんじゃなかったっけ?
-
timeout
多すぎやろ - もっと良い方法あるよね
というわけでこれをさらに改善することにしました。
改善
- そもそも
fireEvent
よりはuserEvent
使ったほうがいいらしい、ということなのでそちらに変更
- これでテストが通ったので、
timeout
処理もなるべく削除
改善後のコード全体
addRecord.test.jsx
import '@testing-library/jest-dom';
import { screen, render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from '../App';
import { TotalTimeProvider } from '../Providers/TotalTimeProvider';
import { addRecord, getAllLogs } from '../utils/supabaseFunctions';
const mockLogs = [{ id: 1, title: 'test', time: 1 }];
jest.mock('../utils/supabaseFunctions', () => ({
getAllLogs: jest.fn(() => Promise.resolve(mockLogs)),
addRecord: jest.fn((title, time) => {
const newLog = { id: mockLogs.length + 1, title, time };
mockLogs.push(newLog);
return Promise.resolve(newLog);
}),
}));
describe('動作テスト', () => {
it('記録追加', async () => {
const user = userEvent.setup();
render(
<TotalTimeProvider>
<App />
</TotalTimeProvider>
);
await waitFor(() => {
expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByTestId('log')).toHaveTextContent('test 1時間');
});
await user.type(screen.getByLabelText('学習内容'), 'test2');
await user.type(screen.getByLabelText('学習時間'), '2');
await user.click(screen.getByText('登録'));
await waitFor(() => {
const logs = screen.getAllByTestId('log');
expect(logs).toHaveLength(2);
});
const logs = screen.getAllByTestId('log');
expect(logs[1]).toHaveTextContent('test2 2時間');
expect(addRecord).toHaveBeenCalledWith('test2', 2);
expect(getAllLogs).toHaveBeenCalledTimes(2);
});
});
おわりに
これで一応エラーも解消し、ついでにコードもすっきりしてめでたしめでたし……と言いたいところなのですが、結局一度通過したテストがエラー吐いた理由がはっきりせず、その点はもやもやしています。