0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React】GitHub Actionsで通っているテストがローカルで通らなくなった

Last updated at Posted at 2025-02-23

はじめに

ローカルで通ったテストがデプロイ環境では通らない、ということはよくあるようで、検索すると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画面が消えるまで次の処理を待つようにした
  • renderfireEventactで囲んだ
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);
  });
});

おわりに

これで一応エラーも解消し、ついでにコードもすっきりしてめでたしめでたし……と言いたいところなのですが、結局一度通過したテストがエラー吐いた理由がはっきりせず、その点はもやもやしています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?