Test
[Jest] React + Vite + TypeScript 프로젝트에 Jest 테스트 환경 구축
jalalja
2024. 12. 16. 23:45
BDD (Behavior-Driven Development) 란?
더보기
- BDD 란?
- 구현에 중점을 두기보다는 동작에 중점을 두는 테스트 개발 방식이다.
- 사용자가 원하는 결과를 정확히 표현하는것이 목적이다.
- 테스트는 개발자와 비즈니스 이해 관계자 모두가 읽을 수 있게 작성한다.
- React 테스트는 주로 BDD 방식으로 진행한다.
React + Vite + TypeScript 프로젝트 생성
더보기



- Vite 프로젝트 생성
yarn create vite
- 프로젝트 이름 입력

- React 선택

- TypeScript + SWC 선택
- SWC (Speedy Web Compiler) : JavaScript 코드 중 ES6 이상으로 작성된 최신 문법을 ES5로 트랜스파일 하거나, TypeScript 코드를 JavaScript 코드로 컴파일하는데 사용되는 빌드 도구이다.

- node_modules 생성
yarn install
Vitest가 아닌 Jest를 선택한 이유
더보기

- Jest를 선택한 이유
- 프로젝트가 Vite로 생성되었지만, Jest를 선택한 이유는 Vite와의 호환성뿐만 아니라 범용성을 고려했기 때문이다.
- Vitest에 비해 Jest는 더 널리 사용되고 다양한 커뮤니티와 도구 지원을 갖춘 테스트 프레임워크라는 인식도 존재했다.
- 또한, 추후 마이그레이션을 고려할 때 Jest는 다양한 환경과의 호환성이 뛰어나고, 널리 채택된 표준이기 때문에 보다 유연하고 지속 가능한 선택이라고 판단했다.
- 이러한 이유로 Vite의 이점을 살리면서도, 향후 다른 환경으로의 전환에 대비해 Jest를 선택하는 것이 더 합리적이라고 생각했다.
- Jest 공식 홈페이지에서도 Vite 환경에서 Jest를 사용하기에는 지원하지 않는 부분이 존재한다고 하였다.

- 참고
vite-jest/packages/vite-jest at main · haoqunjiang/vite-jest
First-class Vite integration for Jest. Contribute to haoqunjiang/vite-jest development by creating an account on GitHub.
github.com
Jest 설치 (실제 프로젝트에 적용한 케이스)
더보기


- jest, ts-jest, @types/jest , @jest/globals 설치
- jest : Jest를 사용하기 위한 핵심 라이브러리로 테스트 실행, 매칭 함수 등을 제공한다.
- ts-jest : TypeScript로 작성된 프로젝트를 Jest로 테스트할 수 있게 해주는 Jest 변환기이다.
- @types/jest : Jest에 대한 타입 정의를 위한 모듈이다.
- @jest/globals : Jest의 테스트 함수들을 import 하여 명시적으로 사용하는 코드를 확인할 수 있다.
yarn add -D jest ts-jest @types/jest @jest/globals
- jest 설정 파일 생성
yarn ts-jest config:init
- jest.config.js 설정 파일 변경
- testEnvironment: "jsdom"
- Jest 테스트를 어떤 가상 환경에서 실행할지에 대한 설정이다.
- node : Node.js 환경에서 테스트를 실행한다.
- jsdom : DOM API를 포함하는 브라우저와 유사한 환경에서 테스트를 실행한다.
- tsconfig : "tsconfig.app.json"
- tsconfig.app.json 파일을 인식 못하는 문제가 가끔 생겨서 명시적으로 작성해준다.
- tsconfig.node.json : 노드에서 사용하는 설정 파일이다.
- tsconfig.app.json : 브라우저에서 사용하는 설정 파일이다.
- setupFiles: ["./jestSetup.ts"]
- jestSetup.ts : Jest가 테스트를 실행하기 전에 특정 작업을 수행할 수 있도록 도와주는 역할을 한다. (Jest의 BeforAll 함수와 동일한 역할 제공)
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
testEnvironment: "jsdom",
transform: {
"^.+.tsx?$": [
"ts-jest",
{
tsconfig: "tsconfig.app.json",
},
],
},
setupFiles: ["./jestSetup.ts"],
};
- jestSetup.ts 설정 파일 변경
- react에서 제공하는 act() 함수를 동작시키기 위해서는 필수적으로 설정해주어야 한다.
- act()
- act()는 인자로 받은 함수를 실행시켜 가상의 DOM(jsdom)에 적용하는 역할을 한다.
- act()는 테스트 중 React 컴포넌트의 상태 변화가 DOM에 완전히 반영되었는지를 보장하기 때문에 React가 브라우저에서 실행될 때와 최대한 비슷한 환경에서 테스트할 수 있다.
- 추후에 testing-library/react를 사용해 보다 편하게 act()를 사용할 수 있다.
(global as any).IS_REACT_ACT_ENVIRONMENT = true;
- jestSetup.ts 설정 파일에서 as any 사용시 error가 발생하는 상황

- 원인
- TypeScript와 ESLint를 함께 사용할 때 발생하는 문제로, 코드에서 any 타입을 사용했기 때문에 나타난다.
- ESLint의 @typescript-eslint/no-explicit-any 규칙은 명시적으로 any 타입을 사용하는 것을 방지하기 때문이다.
- 해결
- eslint.config.js 파일의 rules 부분에 해당 설정을 off로 지정한다.
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"@typescript-eslint/no-explicit-any": "off", // 추가
},
- 전체 폴더 구조

Jest 설치 (기록용)
더보기






- jest, ts-jest, @types/jest, @jest/globals 설치
- jest : Jest를 사용하기 위한 핵심 라이브러리로 테스트 실행, 매칭 함수 등을 제공한다.
- ts-jest : TypeScript로 작성된 프로젝트를 Jest로 테스트할 수 있게 해주는 Jest 변환기이다.
- @types/jest : Jest에 대한 타입 정의를 위한 모듈이다.
- @jest/globals : Jest의 테스트 함수들을 import 하여 명시적으로 사용하는 코드를 확인할 수 있다.
yarn add -D jest ts-jest @types/jest @jest/globals
- jest 설정 파일 생성
yarn jest --init
- Would you like to use Jest when running "test" script in "package.json"? => yes
- package.json의 script에 "test": "jest" 코드를 추가할지 묻는 질문이다.
- 해당 스크립트를 통해 yarn test를 사용해서 jest를 실행할 수 있다. (yarn jest로도 실행이 가능하기때문에 no로 설정해도 무방하다)

- Would you like to use Typescript for the configuration file? => yes
- Jest 설정 파일을 TypeScript(jest.config.ts)로 생성할지 묻는 질문이다.
- 프로젝트가 TypeScript를 사용한다면 yes로 설정해주는 것이 좋다. (no를 선택할 경우 js 파일로 생성된다)

- Choose the test environment that will be used for testing => js-dom (browser-like)
- Jest 테스트를 어떤 가상 환경에서 실행할지를 묻는 질문이다.
- node : Node.js 환경에서 테스트를 실행한다. (Node.js 기반 프로젝트에 적합하다)
- jsdom : DOM API를 포함하는 브라우저와 유사한 환경에서 테스트를 실행한다. (프론트엔드 프로젝트에 적합하다)

- Do you want Jest to add coverage reports? => yes
- 테스트 실행 시 코드 커버리지 리포트를 생성할지 묻는 질문이다. (프로젝트의 품질을 보다 높이고 싶다면 사용하는것이 좋다)

- Which provider should be used to instrument code for coverage? => v8
- Jest에서 코드 커버리지를 측정하기 위해 어떤 도구를 사용할지 선택하는 질문이다.
- v8을 선택한 이유 : v8이 babel보다 빠르고 효율적이고, vite 프로젝트는 babel을 사용하지 않고 ESBuild를 사용하기 때문이다.

- Automatically clear mock calls, instances, contexts and results before every test? => yes
- Jest 테스트 프레임워크에서 mock 함수의 상태를 자동으로 초기화할지 여부를 묻는 질문이다.
- 테스트간의 독립성을 보장하기 위해서 사용하는것이 좋다. (사용을 하지 않을 경우에는 jest.clearAllMocks() 함수를 사용해 직접 초기화해줘야 한다)

기본 테스트 (React Testing Library 사용하지 않은 경우)
더보기




- App.tsx 코드
- 기본적인 로그인 화면을 보여준다. (디자인 X)
import { FormEvent, useState } from "react";
import "./App.css";
function App() {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [loginError, setLoginError] = useState<boolean>(false);
const onSubmit = (e: FormEvent) => {
e.preventDefault();
if (!email || !password) {
setLoginError(true);
return;
}
};
return (
<>
<header>
<h1>HEADER</h1>
</header>
{loginError && <p id="errorMessage">ERROR</p>}
<main>
<form onSubmit={onSubmit}>
<div>
<h2>email</h2>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<h2>password</h2>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button id="submitButton" type="submit">
Form 제출
</button>
</form>
</main>
<footer>
<h1>FOOTER</h1>
</footer>
</>
);
}
export default App;
- 렌더링 테스트 (App.test.tsx)
- header 태그 안에 있는 h1 태그의 텍스트가 HEADER인지 확인하는 테스트를 수행한다. (렌더링이 잘 되는지 확인하기위한 용도)
import { test } from "@jest/globals";
import { act } from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
test("로그인 화면이 잘 보인다.", async () => {
const container = document.createElement("div");
await act(() => {
ReactDOM.createRoot(container).render(<App />);
});
expect(container.querySelector("header h1")?.textContent).toBe("HEADER");
});
- 테스트 실행 시 모듈 관련 에러가 발생하는 상황

- 원인
- jest-environment-jsdom 모듈이 설치가 되어있지 않아서 생기는 문제로 보인다.
- 해결
- 해당 모듈을 설치해준다.
yarn add -D jest-environment-jsdom
- 테스트 실행 시 CSS 파일 관련 에러가 발생하는 상황

- 원인
- jest가 CSS 파일을 처리할 수 없어서 발생한 문제다.
- 해결 1.
- App.tsx에서 import한 App.css 파일을 제거하거나, App.css파일 내부에서 초기 생성된 모든 클래스를 제거하면 에러가 해결된다.
- 해결 2.
- jest는 기본적으로 JavaScript 외에는 처리를 하려고하지 않는다.
- JavaScript가 아닌 파일(CSS 등)을 가져올 때 오류를 방지할 수 있는 jest-transform-stub 모듈을 받는다.
- 그리고 jest.config.js 파일에 "^.+\\.css$": "jest-transform-stub" 설정을 추가해준다.
yarn add -D jest-transform-stub
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
testEnvironment: "jsdom",
transform: {
"^.+.tsx?$": [
"ts-jest",
{
tsconfig: "tsconfig.app.json",
},
],
"^.+\\.css$": "jest-transform-stub", // 추가
},
setupFiles: ["./jestSetup.ts"],
};
- 테스트 결과 (성공)

- 인터렉션 테스트 (App.test.tsx)
- form 제출 버튼 클릭 시 email, password 중 입력이 되지 않은 부분이 있을 경우 에러 메시지가 보여지는지 확인하는 테스트를 수행한다.
import { test } from "@jest/globals";
import { act } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
test("이메일, 비밀번호 미입력시 에러메시지가 나타난다.", async () => {
// 테스트를 위한 #root와 동일한 요소 생성
const container = document.createElement("div");
// 해당 요소를 body 하단에 삽입
document.body.appendChild(container);
// container 안에 App 컴포넌트 렌더링
await act(() => {
createRoot(container).render(<App />);
});
// form을 제출할 버튼 가져오기
const submitButton = container.querySelector("#submitButton");
// 에러 메시지 가져오기
let errorMessage = container.querySelector("#errorMessage");
// form을 제출하기 전에는 에러 메시지가 없기때문에 undefined여야 한다.
expect(errorMessage?.textContent).toBeUndefined();
// form 제출 버튼 클릭 이벤트
await act(() => {
submitButton?.dispatchEvent(new MouseEvent("click"));
});
// form을 제출한 다음에 변한 에러 메시지를 가져온다.
errorMessage = container.querySelector("#errorMessage");
// 에러 메시지는 ERROR여야 한다.
expect(errorMessage?.textContent).toBe("ERROR");
});
- 테스트 결과 (성공)

React Testing Library 사용