Redux Toolkit에서 createAsyncThunk 어떻게 테스트할까?

mechaniccoder·2021년 7월 13일
3

react와 redux toolkit을 활용해 테스트 코드 작성하는 방법을 공부하던 도중에 redux toolkit이 제공하는 createAsyncThunk를 테스트해야만 했습니다. 비동기 처리 로직을 담당하는 api이기 때문에 어떻게 테스트해야 하는지 고민을 했고 어떻게 결론에 도달했는지 공유합니다.

Test flow

export const fetchOneUser = createAsyncThunk<fetchOneUserData, string, { state: InitialState }>(
  'user/fetchOneUser',
  async (userId, { rejectWithValue }) => {
    try {
      const res = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
      return res.data;
    } catch (err) {
      return rejectWithValue(err);
    }
  },
);

위와 같은 thunk함수를 작성했습니다. 리액트 컴포넌트에서 이 로직을 실행하기 위해서는 dispatch(fetchOneUser('1')) 이렇게 함수를 dispatch해줘야 합니다. thunk 미들웨어가 액션이 함수인 것을 보고 그에 맞는 로직을 실행할 겁니다.

이렇게 비동기 로직이 실행되면 두가지 시나리오를 가집니다.

  • pending -> fulfilled
  • pending -> rejected

저는 redux-mock-store 라이브러리를 활용해 thunk액션이 발생했을때 위의 두 시나리오의 액션이 발생했는지를 테스트했습니다.

import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';

const middlewares = [thunk];
const mockStore = configureStore(middlewares);

redux-thunk와 redux-mock-store를 설치하고 mockStore를 생성합니다.

pending -> fulfilled

it('success', async () => {
    const getMock = axios.get as jest.Mock;
    (axios.get as jest.Mock).mockImplementationOnce(() => ({
      data: {
        username: 'seunghwan',
        email: 'seunghwan@orbisai.co',
      },
    }));

    const store = mockStore({});
    await store.dispatch(fetchOneUser('1') as any);

    expect(store.getActions()[0].type).toEqual(fetchOneUser.pending.type);
    expect(store.getActions()[1]).toEqual(
      expect.objectContaining({
        type: fetchOneUser.fulfilled.type,
        payload: { username: 'seunghwan', email: 'seunghwan@orbisai.co' },
      }),
    );
  });
  • 실제 네트워크를 타지않도록 axios를 mocking했고, thunk로직이 잘 실행되도록하기 위해 reponse값을 mockImplementationOnce로 명시해줬습니다.

  • 이렇게 명시적으로 리턴한 response값을 포함하고 있는지 확인하기 위해 expect.objectContaining을 활용했습니다.

pending -> rejected

it('failed', async () => {
    const store = mockStore({});

    (axios.get as jest.Mock).mockImplementationOnce(() => {
      throw {
        response: {
          data: {
            message: 'Error: fetch user',
          },
        },
      };
    });

    await store.dispatch(fetchOneUser('2') as any);
    expect(store.getActions()[0].type).toEqual(fetchOneUser.pending.type);
    expect(store.getActions()[1]).toEqual(
      expect.objectContaining({
        payload: {
          response: {
            data: {
              message: 'Error: fetch user',
            },
          },
        },
      }),
    );
  });
  • rejected이 발생하는 시나리오도 첫번째 시나리오랑 크게 다르지 않습니다.

이슈

  • createAsyncThunk로 생성한 액션과 redux-mock-store의 dispatch에 전달할 액션의 타입이 맞지 않아서 any로 type assertion하여 테스트했는데 적당한 솔루션이 있는지 조사해봐야할 것 같습니다.

  • 실제로 redux store의 값을 의도한대로 변경하는지를 테스트해야 합니다. 이는 다음에 포스팅하도록 해보겠습니다.

profile
세계 최고 수준을 향해 달려가는 개발자입니다.

0개의 댓글