본문 바로가기

STUDY/기타

Jest와 React

Jest란

 

가장 대표적인 JavaScript 테스트 프레임 워크이다. Jest 이전에는 JavaScript 테스트에 필요한 Test Runner(mocha), Test Matcher(sinon), Mocking(test mock) 라이브러리들을 조합하여 테스트를 진행해야 했다. 하지만 Jest는 All-in-one 테스트 프레임 워크로서 쉽게 구성하고 사용할 수 있다.

 

 


CRA(Create React App)에서의 Jest

 

Jest를 사용하기 위해서는 관련 라이브러리와 스크립트 설정등 화녕 구성이 필요하지만 CRA로 만들어진 react 앱에는 모든 내용이 포함되어있다. 그리고 App의 테스트 코드가 들어있는 App.test.ts까지 만들어져 있다.

 

import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

 

 


Vite에서의 Jest

 

Using Vite

Jest can be used in projects that use vite to serve source code over native ESM to provide some frontend tooling, vite is an opinionated tool and does offer some out-of-the box workflows. Jest is not fully supported by vite due to how the plugin system from vite works, but there are some working examples for first-class jest integration using vite-jest, since this is not fully supported, you might as well read the limitation of the vite-jest. Refer to the vite guide to get started.

Jest 공식문서에도 나와있듯이 아직 Vite에서는 Jest를 완벽하게 지원하지 않는다. 따라서 부가적인 패키지 설치와 설정이 필요하다.

 

> yarn create vite subproject --template 
> yarn add -D jest jest-environment-jsdom jest-svg-transformer identity-obj-proxy
> yarn add -D @testing-library/react @testing-library/jest-dom @testing-library/user-event
> yarn add -D @babel/preset-react @babel/preset-typescript @babel/preset-env

Vite로 프로젝트를 생성한 이후 jest, testing-library, babel 관련 패키지를 설치한다.

 

// babel.config.cjs
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current",
        },
      },
    ],
    "@babel/preset-react",
    "@babel/preset-typescript",
  ],
};

 

// jest-setup.ts
import "@testing-library/jest-dom";

 

// package.json
...
"scripts": {
    ...
    "test": "jest"
  },
...
"jest": {
  "testEnvironment": "jsdom",
  "setupFilesAfterEnv": [
    "<rootDir>/jest-setup.ts"
  ],
  "moduleNameMapper": {
    "\\.(css|less)$": "identity-obj-proxy",
    "\\.svg": "jest-svg-transformer"
  }
}
...

이후 babel.config.cjs, jest-setup.ts 파일을 생성해주고, pacakge.json 파일을 수정해준다.

 

 


Jest 테스트 실행

 

> yarn test
...
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.391 s, estimated 1 s
...

Jest 환경 구성이 되어있고 테스트 코드가 있다면 npm test / yarn test로 테스트 코드를 실행할 수 있다. __tests__ 폴더에 있는 파일, .test.ts 또는 .spec.ts로 이름이 끝나는 파일에 있는 테스트 코드가 실행 된다.

 

WebStorm 설정을 통해 Jest CLI가 아니라 WebStorm GUI로 실행하도록 설정 할 수 있다.

 

 


테스트 코드 구성

 

// Sum.test.js 파일
import sum from 'Sum.js';

test('test sum function', () => {
  const result = sum(1, 1)
  expect(result).toBe(2);
});

위는 아주 기본적인 Jest의 테스트 코드이다. test 메소드로 테스트를 실행하고  description과 수행할 테스트 코드가 두개의 파라미터로 전달된다. 위 예제에서는 sum(1, 1)의 결과값인 result를 expect로 검사할 대상으로 지정하고, toBe를 통해 2와 같은지 검사하였다.

 

 


Matcher

 

Jest에서는 Matcher를 사용하여 검사할 대상이 올바른 값과 일치하는지 판별한다. null인지 아닌지, 특정한 숫자 또는 문자열인지, 배열이라면 길이가 얼마인지, 특정한 예외를 던지는지 등을 Matcher로 검사한다.

 

// 다양한 종류의 Matcher
test('null', () => {
  const n = null;
  expect(n).toBeNull();
  expect(n).toBeDefined();
  expect(n).not.toBeUndefined();
  expect(n).not.toBeTruthy();
  expect(n).toBeFalsy();
});

 

https://jestjs.io/docs/using-matchers

 

Using Matchers · Jest

Jest uses "matchers" to let you test values in different ways. This document will introduce some commonly used matchers. For the full list, see the expect API doc.

jestjs.io

 

 


Jest의 라이프 사이클

 

// database 테스트 코드
const globalDatabase = makeGlobalDatabase();

function cleanUpDatabase(db) {
  db.cleanUp();
}

afterAll(() => {
  cleanUpDatabase(globalDatabase);
});

test('can find things', () => {
  return globalDatabase.find('thing', {}, results => {
    expect(results.length).toBeGreaterThan(0);
  });
});

test('can insert a thing', () => {
  return globalDatabase.insert('thing', makeThing(), response => {
    expect(response.success).toBeTruthy();
  });
});

위 테스트 코드를 보면 'can insert a thing' 테스트에서 db에 데이터를 삽입하고 있다. 그리고 afterAll 메소드에서 cleanUpDatabase 메소드를 호출하여 삽입된 데이터를 정리하고 있다. 이처럼 jest에는 여러 테스트 라이프 사이클에 관한 메소드 들이 있다.

 

afterAll(fn, timeout) // 동일 파일에 있는 모든 테스트가 끝나면 실행
afterEach(fn, timeout) // 동일 파일에 있는 각 테스트가 끝날때마다 실행
beforeAll(fn, timeout) // 동일 파일에 있는 모든 테스트가 실행되기 전에 실행
beforeEach(fn, timeout) // 동일 파일에 있는 각 테스트가 실행되기 전마다 실행
describe(name, fn) // 여러 테스트를 하나의 블록으로 지정

 

https://jestjs.io/docs/api

 

Globals · Jest

In your test files, Jest puts each of these methods and objects into the global environment. You don't have to require or import anything to use them. However, if you prefer explicit imports, you can do import {describe, expect, test} from '@jest/globals'.

jestjs.io

 

 


React App 테스트  - Snapshot Testing

 

> npm install --save-dev react-test-renderer
> npm install --save-dev @types/react-test-renderer
> npm install --save-dev enzyme

Ract App을 테스트 하기 위해서 렌더링/액션 관련 라이브러리를 추가로 설치해야 한다. 

 

import React from "react";
import App from "./App";
import renderer from "react-test-renderer";

test("snapshot test", () => {
  const appComponent = renderer.create(<App />);
  const tree = appComponent.toJSON();
  expect(tree).toMatchSnapshot();
});

 

// __snapshot__/App.test.tsx.snap 파일
exports[`snapshot test 1`] = `
<div>
	App Test
</div>
`;

Snapshot Testing은 컴포넌트의 모습을 스냅샷으로 기억해 두었다가 이후에 해당 컴포넌트가 스냅샷과 비교하여 달라진 것이 있는지 검사하는 것을 말한다. 위의 테스트 코드를 yarn test로 실행하면 __snapshots__ 폴더에 App 컴포넌트의 스냅샷이 생성된다.

 

> yarn test
FAIL  src/App.test.tsx
  ✕ snapshot test 
...  
      </div>
      - App Test
      </div>
...

한번 스냅샷이 생성된 이후에 App 컴포넌트를 변경하고 다시 yarn test를 실행하면 스냅샷과 현재 모습이 달라서 테스트에 FAIL한다.

 

 


React App 테스트  - Dom Testing

 

React 컴포넌트를 렌더링 하고 상호작용하며 변화되는 모습을 테스트하고 싶다면 Dom Testing을 해야한다.

 

// CheckboxWithLabel.js
import {useState} from 'react';

export default function CheckboxWithLabel({labelOn, labelOff}) {
  const [isChecked, setIsChecked] = useState(false);

  const onChange = () => {
    setIsChecked(!isChecked);
  };

  return (
    <label>
      <input type="checkbox" checked={isChecked} onChange={onChange} />
      {isChecked ? labelOn : labelOff}
    </label>
  );
}

 

import {fireEvent, render} from '@testing-library/react';
import CheckboxWithLabel from './CheckboxWithLabel';

it('CheckboxWithLabel changes the text after click', () => {
  const {queryByLabelText, getByLabelText} = render(
    <CheckboxWithLabel labelOn="On" labelOff="Off" />,
  );

  expect(queryByLabelText(/off/i)).toBeTruthy();

  fireEvent.click(getByLabelText(/off/i));

  expect(queryByLabelText(/on/i)).toBeTruthy();
});

클릭할 때 마다 텍스트가 바뀌는 Checkbox 형태의 컴포넌트를 테스트하는 코드이다. 컴포넌트를 렌더링 한 이후, click 이벤트를 발생시켜서 변화한 컴포넌트가 모습까지 테스트 하고 있다. 

 

보다 직관적이고 편리하게 Dom 테스트를 할 수 있게 해주는 Enzyme라는 라이브러리가 있지만 react 16 이후 지원이 끊겼다.

 

 


Watch 모드
 
Jest는 테스트를 효율화하기 위해 마지막 커밋 이후 수정된 파일과 관련된 테스트 만을 수행한다. 처음 yarn test를 실행하면 만들어진 모든 테스트가 실행되지만, git commit 이후 yarn test를 실행하면 모든 테스트가 스킵된다.
 
> yarn test
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.359 s, estimated 1 s

> git add .
> git commit -m 'commit message'

> yarn test
No tests found related to files changed since last commit.

 

 

728x90