blog
wanted
pre-on-boarding
Cannot use import statement outside a module
Jest Mocking
jest
try-catch error type

원티드 프리온보딩 과제 - 6일차

Jest 통신 테스트 환경 설정

문제: syntaxerror: Cannot use import statement outside a module 에러 발생

Axios를 설치하고 통신 테스트를 시작하면서 문제가 발생했습니다. 이런 에러메시지를 받았습니다.

시도: syntaxerror: Cannot use import statement outside a module 에러 발생

"Cannot use import statement outside a module" with Axios

해결하기 전 여기까지 검색했습니다. 생각보다 많은 사람들이 문제로 생각하고 있었습니다.

해결

axios 1.1.2 버전 issue ( SyntaxError: Cannot use import statement outside a module) - 인프런

{
  "name": "wanted-pre-onboarding-frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    // 생략
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "msw": "^1.2.1",
    "prettier": "^2.8.7",
    "prettier-plugin-tailwindcss": "^0.2.7",
    "tailwindcss": "^3.3.1"
  }
}

"test": "react-scripts test"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!axios)/\""으로 변경하면 됩니다.

학습

Jest 개발환경은 생각보다 좋지 않습니다. 또 테스트 설정자체를 깊게 공부할 필요도 있을 것 같습니다.

유저의 행동을 테스트하는 방법

describe('CustomButton', () => {
  it('활성화되어 있는 동안에 클릭하면 기능을 수행할 수 있습니다.', () => {
    const mockOnClick = jest.fn();
    render(
      <CustomButton text="회원가입" hierarchy="primary" onClick={mockOnClick} />
    );
    const button = screen.getByRole('button');
    userEvent.click(button);
    expect(mockOnClick).toHaveBeenCalledTimes(1);
  });

  it('비활성화되어 있는 동안에 클릭하면 기능을 수행할 수 없습니다.', () => {
    const mockOnClick = jest.fn();
    render(
      <CustomButton
        text="회원가입"
        hierarchy="primary"
        onClick={mockOnClick}
        disabled={true}
      />
    );
    const button = screen.getByRole('button');
    userEvent.click(button);
    expect(mockOnClick).toHaveBeenCalledTimes(0);
  });
});

disabled props가 정상동작하는지 검증해야 하는 상황입니다. 비활성화에서는 함수가 호출되지 않고 활성화에는 함수가 호출되어야 합니다.

이렇게 유저의 행동을 코드 재현할 수 있습니다. 물론 아주 단순한 클릭만 재현하고 있습니다.

Axios try-catch error 타입지정

문제

async function signin(email: string, password: string) {
  try {
    const res = await client.post(
      AUTH_PATH + SIGNIN_PATH,
      { email, password },
      { headers: { 'Content-Type': 'application/json' } }
    );
    if (res.status === 200) return res.data;
  } catch (error) {
    if (error.response?.status === 401) return '비밀번호가 일치하지 않습니다.';
    if (error.response?.status === 404) return '가입되지 않은 이메일입니다';
  }
}
  • 여기서 errorTypesafe하지 않았습니다.

시도

async function signin(email: string, password: string) {
  try {
    const res = await client.post(
      AUTH_PATH + SIGNIN_PATH,
      { email, password },
      { headers: { 'Content-Type': 'application/json' } }
    );
    if (res.status === 200) return res.data;
  } catch (error: AxiosError) {
    // Catch clause variable type annotation must be 'any' or 'unknown' if specified.
    if (error.response?.status === 401) return '비밀번호가 일치하지 않습니다.';
    if (error.response?.status === 404) return '가입되지 않은 이메일입니다';
  }
}

Catch clause variable type annotation must be 'any' or 'unknown' if specified.

이런 에러메시지를 줍니다.

해결

axios Error typescript, annotation must be 'any' or 'unknown' if?

async function signin(email: string, password: string) {
  try {
    const res = await client.post(
      AUTH_PATH + SIGNIN_PATH,
      { email, password },
      { headers: { 'Content-Type': 'application/json' } }
    );
    if (res.status === 200) return res.data;
  } catch (error) {
    const err = error as AxiosError;
    if (err.response?.status === 401) return '비밀번호가 일치하지 않습니다.';
    if (err.response?.status === 404) return '가입되지 않은 이메일입니다';
  }
}
  • error as AxiosError으로 타입지정하고 err식별자를 따로 만들고 활용했습니다. 이렇게 되면 Typesafe해집니다.

학습

try-catcherrorany 혹은 unknown입니다. 이 타입으로 고정되어 있습니다.

Jest: mocking console.error - tests fails

문제: 너무 많은 에러 메시지

  • 통신과 관련해서 테스트를 시작할 때 에러메시지가 많았습니다.

시도

  • 한번에 검색하고 한번에 해결했습니다.

해결

setupTests.ts를 수정하는 것으로 해결했습니다.

Jest: mocking console.error - tests fails

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

import { server } from './mocks/server';
// Establish API mocking before all tests.
beforeAll(() => server.listen());

const original = console.error;

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => {
  // console.error("you cant see me");
  console.error = original;
  // console.error("now you can");

  return server.resetHandlers();
});

// Clean up after the tests are finished.
afterAll(() => server.close());

beforeEach(() => {
  console.error = jest.fn();
  console.error('you cant see me');
});

최종적으로는 이렇게 되었습니다.

학습: console 객체를 공부합시다.

자주 하는 결심들이 있습니다. 자바스크립트 정규표현식과 에러객체좀 학습하자고 늘 결심만 하고 실천하지 않습니다. 또 결심하고 실천을 언제해야 할지 모르겠습니다. 나중에 자바스크립트 정리하는 프로젝트에 정리할 것 같습니다. 잘 쓰면 상당히 효율적인 객체인데 잘 활용하고 있지 않고 있었습니다.