reactアプリでのテストコード作成の忘備録(コード記述)

# 概要

jestを使って実際にReactのテストコードを作成した際に詰まった部分の忘備録
[前回](https://qiita.com/kubota_ndatacom/items/ef4f61ab6a0dd4c2162f)

# 目次
[1. inputのvalueを空に変更する](#1-inputのvalueを空に変更する)
[2. alertの呼び出しを処理を検知する](#2-alertの呼び出しを処理を検知する)
[3. fetchMockを使ってのAPI処理の記述時の注意点](#3-fetchmockを使ってのapi処理の記述時の注意点)
[4. Routerを使って複数画面の切り替えを実装している場合のテスト](#4-routerを使って複数画面の切り替えを実装している場合のテスト)

# 1. inputのvalueを空に変更する

userEventを使ってinputのvalueを変更する際の記述でtypeはテキストの入力なので空文字を指定してもダメ
clearを使ってvalueを削除する

```diff_javascript:App.test.js
- userEvent.type(screen.getByLabelText('ユーザー名'), '');
+ userEvent.clear(screen.getByLabelText('ユーザー名'));

  const user_name = await screen.queryByLabelText('ユーザー名');
  expect(user_name.value).toBe('');
```

# 2. alertの呼び出しを処理を検知する

window.alertをモックにして呼び出しを検知する

```diff_javascript:App.test.js

beforeAll(() => {
  window.alert = jest.fn();
});

describe('テストスイート', () => {
  test('アラート確認テスト', async () => {

    userEvent.click(screen.getByRole('button', {name: 'アラート呼び出し'}));
    expect(window.alert).toHaveBeenCalledWith('アラートで呼び出される文字列');

  });
});
```
# 3. fetchMockを使ってのAPI処理の記述時の注意点

fechMockを使ったAPI呼び出し処理のテストにおいて同じ値で呼び出され、別の結果が返ってくる場合のテストケースを記述する際は「fetchMock.restore();」でfetchMockをリセットする必要がある

ただし下記記述の場合、ログイン成功テスト内でエラーになった場合fetchMock.restore()が実行されずに、ログイン失敗テストでfetchMockの呼び出しでエラーになってしまう
[ログイン成功テスト] => fetchMockの呼び出しで定義されたものが無い旨のエラー
[ログイン失敗テスト] => fetchMockにすでに定義されているものがある旨のエラー

```diff_javascript:App.test.js

describe('テストスイート', () => {
  test('ログイン成功テスト', async () => {

    /* 「/login?login_id=test&pass=pass」ではなく「/login?login_id=a&pass=a」で呼び出しがあったと仮定 */

    fetchMock
      .get('/login?login_id=test&pass=pass', {
        result: 1,
      });
    expect(await screen.findByText('メイン画面')).toBeInTheDocument();
+   fetchMock.restore();
  });
  test('ログイン失敗テスト', async () => {

  /* 「/login?login_id=test&pass=pass」で正常に呼び出されたと仮定 */

    fetchMock
      .get('/login?login_id=test&pass=pass', {
        result: 2,
      });
    expect(await screen.queryByText('メイン画面')).toBeNull();
  });
});
```

afterEachを使ってtest実行後に毎回fetchMock.restore()を実行するようにすれば以降のテストでエラーにならなくなる
※ただし処理後に毎回fetchMockがリセットされてしまう為fetchMockのエラーはログに上がらないので注意
[ログイン成功テスト] => expect().toBeInTheDocument();で「メイン画面」が見つからない旨のエラー
[ログイン失敗テスト] => テスト成功

```diff_javascript:App.test.js

+afterEach(() => {
+  fetchMock.restore();
+});

describe('テストスイート', () => {
  test('ログイン成功テスト', async () => {

    /* 「/login?login_id=test&pass=pass」ではなく「/login?login_id=a&pass=a」で呼び出しがあったと仮定 */

    fetchMock
      .get('/login?login_id=test&pass=pass', {
        result: 1,
      });
    expect(await screen.findByText('メイン画面')).toBeInTheDocument();
-   fetchMock.restore();
  });
  test('ログイン失敗テスト', async () => {

    /* 「/login?login_id=test&pass=pass」で正常に呼び出されたと仮定 */

    fetchMock
      .get('/login?login_id=test&pass=pass', {
        result: 2,
      });
    expect(await screen.queryByText('メイン画面')).toBeNull();
  });
});
```

# 4. Routerを使って複数画面の切り替えを実装している場合のテスト

下記の様な構成においてrouterで複数画面を実装している場合のテスト記述方法
※AppからMainへの画面遷移時にuserをstateに入れて渡している

```javascript:index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import App from './App';
import Main from './Main';

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={App} />
        <Route exact path="/Main" component={Main} />
      </Switch>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById('root')
);
```
```javascript:App.js
import React,{ useState,useEffect,Component } from 'react';
import { useHistory,useLocation } from 'react-router-dom';

function App() {
  /* ログイン */
  const login = (login_id, pass) => {
    /* 処理略 */
    history.push({ pathname: '/Main', state: { user: data }});
  }

  return(
    /* 略 */
  );

}
export default App;
```

```javascript:Main.js

import React,{ useState,useEffect,Component } from 'react';
import { useHistory,useLocation } from 'react-router-dom';

const Main = () => {

  /* ユーザーデータ */
  const [user, setUser] = React.useState({});
  const location = useLocation();

  useEffect(() =>{
    setUser(location.state.user)
  },[]);

  return (
    /* 略 */
  );
}
export default Main;
```

createMemoryHistory()を使ってhistoryを作成
データをhistoryにpushしておく事でMain内でuseLocation()を使ってデータを取得出来る


```diff_javascript:Main.test.js

import React,{ useState,useEffect,Component } from 'react';
import { useHistory,useLocation,Router } from 'react-router-dom';
import { render, screen, fireEvent, act, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Main from '../Main';
import fetchMock from 'fetch-mock';
import { createMemoryHistory } from 'history'

describe('テストスイート', () => {
  test('テストケース', async () => {
+    const history = createMemoryHistory();

    const testUserData = {
      user_id: 'test_user_id',
      login_id: 'test_login_id',
      user_name: 'test_user_name',
      pass: 'test_pass'
    };

+    /* useLocation()で使用するデータをhistoryにpushしておく */
+    history.push({ pathname: '/Main', state: { user: testUserData }});

    render(
+      <Router history={history}>
        <Main />
+      </Router>
    );
    /* テスト略 */
  });
});
```