들어가며
[1편]에서 테스트 환경 구축 시 사용되는 패키지의 설정 파일을 살펴보았습니다. [2편]에서는 먼저 Vite, TypeScript, React Testing Library, Jest를 사용해 테스트 환경을 구축하고, 패키지 간 의존성과 설정 파일 문제로 발생했던 에러와 해결책을 공유하고자 합니다.
React 테스트 환경 구축하기(Vite, TS, Jest, React Testing Library)
Vite 프로젝트 설치
npm create vite@latest test-config -- --template react-ts
cd test-config
npm install
code . // vscode 열기
테스트 관련 패키지 설치
// 2-1 Jest 관련 패키지 설치
npm i -D jest jest-environment-jsdom ts-jest jest-transform-stub @types/jest
// 2-2 React Testing Library 관련 패키지 설치
npm i -D @testing-library/react @testing-library/jest-dom
Jest 관련 패키지 설치
npm i -D jest jest-environment-jsdom ts-jest jest-transform-stub @types/jest
- jest-environment-jsdom
- Jest v.28 이상부터 jsdom이 더 이상 기본 환경이 아님
- 따라서 해당 패키지를 설치하고 jest.config.js에 testEnvironment 추가해야 jsdom을 전역적으로 사용 가능
- ts-jest
- JavaScript만 지원하는 Jest가 TypeScript를 이해할 수 있도록 변환하는 역할.
- 사용하는 Jest와 호환되는 버전을 설치(중요)
- Jest에서 TypeScript를 사용하기 위해 @babel/preset-typescript를 사용할 수도 있지만, Babel의 역할은 TypeScript를 JavaScript로 트랜스파일링하는 것이기 때문에 TypeScript 컴파일러 tsc처럼 완전한 타입체크를 하지 않는다는 단점이 있다. 따라서 완전한 타입 체킹을 하려면 ts-jest를 사용하거나 tsc를 따로 실행해야 한다.
- @types/jest
- jest의 타입 패키지로, test, expect와 같은 Jest global API의 타이핑을 위해 필요. 아래와 같이 필요한 인자들을 알려준다.
- Jest 버전을 해당 패키지와 최대한 유사하게 맞춰야 한다. 예를 들어, 만약 Jest 27.4.0을 사용하고 있으면, @types/jest 27.4.x를 사용하는 것이 좋다.

- jest-transform-stub
- Jest는 JavaScript가 아닌 asset을 처리하지 않기 때문에 nonJS 파일(svg, css 등)을 import 할 때 해당 모듈로 에러 방지
- 환경 설정 글에서 자주 보이는 svg 처리를 위한 jest-svg-transformer는 Jest 29와는 호환되지 않으므로 주의
React Testing Library 관련 패키지 설치
npm i -D @testing-library/react @testing-library/jest-dom
- @testing-library/react
- 코어인 @testing-library/dom을 React에 맞춘 패키지
- @testing-library/jest-dom
- Jest 기본 matcher 이외 jest의 DOM에 특화된 matcher 패키지
jest-environment-jsdom과 @testing-library/jest-dom 둘 다 필요한가요?
jsdom과 jest-dom이 이름이 비슷해 헷갈릴 수 있을 것 같습니다.
jest-environment-jsdom은 브라우저 없이 테스트를 실행하기 위한 가상 DOM(jsdom) 패키지이고,
@testing-library/jest-dom은 toBeInTheDocument(), toBeChecked()와 같이 jsDOM 요소를 찾기 위한 matcher(jest-dom)입니다. 따라서 둘 다 설치하여 사용하면 됩니다.
Jest 설정 파일 작성
npm init jest@latest 또는 루트에 직접 jest.config.ts 또는. js 파일을 만들어 작성합니다.
// jest.config.json
import type { Config } from "jest";
const config: Config = {
testEnvironment: "jsdom", // 테스트 시 jsdom 환경 사용
preset: "ts-jest", // TypeScript 사용
transform: {
".+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$": "jest-transform-stub", // nonJS 파일 처리
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], // 모든 테스트 파일에 jest-dom import
testMatch: ["<rootDir>/**/?(*.)+(spec|test).[tj]s[x]"], // 검사할 파일 형식 정의
clearMocks: true, // 모든 테스트 실행 전 자동으로 mock 호출, 인스턴스, 컨텍스트, 결과값 클리어
collectCoverage: true, // 테스트 실행 동안 커버리지 정보 수집
coverageDirectory: "coverage", // 커버리지 파일 output 디렉토리
coverageProvider: "v8", // 커버리지 확인을 위해 사용할 프로바이더
};
export default config;
루트에 jest.setup.ts 파일을 작성합니다.
import "@testing-library/jest-dom";
Vite의 public 경로를 사용하기 위해 jest에 public alias를 설정합니다.
// jest.config.json
const config: Config = {
...
moduleNameMapper: {
"^@public/(.*)$": "<rootDir>/public/$1",
},
}
// App.tsx
...
import viteLogo from "@public/vite.svg";
function App() { ... }
export default App;
Vite에서는 public 폴더의 asset을 절대 경로로 접근할 수 있지만, Jest는 이를 읽지 못합니다. 따라서 jest.config에서 alias를 지정해 경로를 읽을 수 있도록 합니다. 사용된 정규표현식은 @public으로 시작하는 경로에서 / 이후의 (.*) 부분을 $1에 적용합니다.
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"esModuleInterop": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
- jsx 속성을 react-jsx를 react로 수정
- react: TypeScript가 JSX 생성 함수를 React.createElement로 추정하고, 사용되지 않은 import에 대해 warning을 제기 X
- react-jsx: TypeScript가 사용자 정의 React 변수를 제공할 것이라고 추정해 React를 파일에서 사용해야 함
- 따라서 TypeScript 환경에서 React를 import 하고 사용하지 않아도 테스트 에러가 나지 않도록 jsx: “react”로 지정
- esModuleInterop을 true로 설정
- esModuleInterop은 CommonJS와 ES modules를 모두 사용하는 환경에서 Typescript가 두 가지 다른 모듈 시스템에 모두 호환되도록 트랜스파일
- Jest와 같이 CommonJS를 사용하는 third-party 라이브러리를 사용할 때 필요
테스트 작성
루트에 테스트 폴더를 생성하고, 테스트를 작성합니다.
// src/App.tsx
import React from "react";
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
);
}
export default App;
// tests/App.test.tsx
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import App from "../src/App";
test("버튼 초기 상태 확인", () => {
render(<App />);
const counterButton = screen.getByRole("button", { name: /count/i });
expect(counterButton.textContent).toBe("count is 0");
});
test("버튼을 클릭하면 1이 증가한다.", () => {
render(<App />);
const counterButton = screen.getByRole("button", { name: /count/i });
fireEvent.click(counterButton);
expect(counterButton.textContent).toBe("count is 1");
});

테스트 성공!!
멀티 프로젝트 혹은 모노레포 테스트하기
지금까지 하나의 프론트엔드 프로젝트에서 테스트 환경을 구축하는 방법을 살펴보았습니다. 이제부터 여러 프로젝트가 있는 경우 테스트 환경을 구성해 보겠습니다. 이 경우는 크게 다르지 않지만, 패키지가 설정 파일을 찾고, 실행하는 방법을 이해해야 합니다.
프론트엔드와 백엔드 폴더가 하나의 프로젝트에 주어진다고 가정해보겠습니다. 루트에 있는 package.json에 프론트(frontend)와 백엔드(src)가 사용하는 패키지를 설치하고 공유합니다. 프론트엔드 폴더는 React 테스트 환경 구축하기에서 진행했던 구성과 동일합니다.

[1편]에서 살펴본 것처럼 각 패키지는 현재 작업 폴더(동작을 실행할 대상 폴더) 내에 설정 파일이 있는지 확인하고, 없으면 상위 폴더로 올라가면서 설정 파일을 찾습니다.
그렇다면 현재 작업하고 있는 폴더는 어떻게 알 수 있을까요? 이는 config 파일 설정이나 pacakage.json의 위치에 따라 다릅니다. 예를 들어, Jest는 package.json이 위치한 폴더 내의 모든 테스트 파일을 재귀적으로 실행합니다. 위의 경우 pacakge.json이 있는 루트 폴더에 있는 테스트, 즉 tests 폴더 내의 테스트 파일들을 실행할 것입니다. frontend내에도 test.ts[x] 파일을 가진 tests 폴더가 있다면 마찬가지로 실행합니다.
// pacakge.json
"scripts": {
"start": "nodemon server.js",
"test": "jest --watch",
"dev": "cd frontend && vite",
"build": "cd frontend && tsc && vite build",
"lint": "cd frnotend && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
npm test로 테스트를 실행할 때 테스트에 TypeScript와 ESLint 설정을 적용하기 위해 루트에 tsconfig와 eslintrc 파일을 생성합니다.
frontend 내부의 tsconfig 파일을 루트로 옮기고, 일부 수정합니다.


tsconfig, eslintrc
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "node", // node 모듈로 수정
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"esModuleInterop": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
// 포함할 폴더 지정(프론트엔드만 TypeScript 사용)
"include": ["frontend/src", "frontend/test"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// eslintrc.cjs
module.exports = {
root: true,
env: {
node: true,
jest: true,
},
parserOptions: {
ecmaVersion: 8,
},
extends: ['eslint:recommended'],
rules: {
'no-undef': 'off'
},
};
jest.config
이 부분이 가장 크게 차이가 나는데, projects 속성을 사용해 각 프로젝트에 따라 아래처럼 다른 설정을 해줍니다. 프론트엔드와 백엔드에서 실행할 테스트 파일 포맷을 지정하고, 필요한 설정들을 작성합니다.
// jest.config.ts
"use strict";
module.exports = {
projects: [
{
displayName: "frontend",
testEnvironment: "jest-environment-jsdom",
preset: "ts-jest",
setupFilesAfterEnv: ["<rootDir>/frontend/jest.setup.ts"],
transform: {
"^.+\\.tsx?$": "ts-jest",
".+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$": "jest-transform-stub",
},
},
{
displayName: "backend",
testMatch: ["<rootDir>/tests/**/?(*.)+(spec|test).[tj]s"],
},
],
};

테스트 성공입니다!
Jest 다운그레이드 버전 사용하기
현재(2024/1/6) 기준으로 Jest 최신 버전은 29.7입니다. 하지만 최신 버전을 사용하지 못하는 경우도 있을 텐데요. 예를 들어, 이전 버전 가운데 하나인 27.5.1 버전을 이미 사용하고 있다고 가정해 보겠습니다. 이때 유의해야 할 점은 다음과 같습니다.
- jest.config.json의 testEnvironment를 jest-environment-jsdom으로 설정
- ts-jest와 TypeScript의 호환 가능한 버전 설치. 예를 들어 jest 27.5.1의 경우 가장 유사한 버전인 ts-jest 27.1, TypeScript 4.2 사용
- 마찬가지로 @types/jest도 버전 맞춰주기
- jest-environment-jsdom 패키지 사용하고 있다면 uninstall. 그렇지 않으면 testEnvironment is undefined 에러 발생
React 테스트 환경 구축 에러 트러블슈팅
사실 위에서 소개한 내용은 해당 에러를 해결하면서 배운 내용입니다. 따라서 앞서 읽었던 것을 복습한다는 가벼운 마음으로 왜 그럴까? 생각해 보면서 읽어 내려가시면 좋을 것 같습니다.

처음으로 돌아가 시작해 봅시다. 이전 경우와 마찬가지로 frontend 폴더에 프론트엔드, src에 백엔드 코드가 있고, pacakge.json을 공유하고 있는 상황입니다. 프론트엔드, 백엔드를 한 프로젝트에서 실행하기 위해 처음 frontend에 vite-react 템플릿을 설치하고 tests 폴더를 추가하면 위와 같은 구조가 나옵니다. 이 상태에서 test에 App.test.tsx를 작성하고 테스트를 실행하면 다음과 같은 에러가 발생합니다.

tsconfig의 옵션인 esModuleInterop을 사용하라는 내용과 —jsx가 설정되지 않아 JSX를 사용할 수 없다는 내용입니다. 즉, tsconfig를 제대로 적용하고 있지 못합니다.

esModuleInterop과 jsx 설정을 변경하였는데도 동일한 에러가 발생합니다. 이는 tsconfig가 frontend 폴더 내에 있기 때문인데요. npm test를 실행했을 때 가장 상위 폴더인 루트에서부터 파일을 찾아가는데 tsconfig가 루트에 없기 때문에 설정을 적용하지 못한 것입니다.

이렇게 루트로 tsconfig를 꺼내고 나면 29 버전에서 테스트가 잘 동작할 것입니다. 여기에서 다운그레이드해보겠습니다.
yarn remove jest ts-jest && yarn add -D jest@27.5.1 ts-jest@27.1
그리고 테스트를 살펴보면 testEnvironmentOptions를 찾을 수 없다는 에러가 납니다.

Jest 버전이 28 이하이기 때문에 jest-environment-jsdom 패키지를 uninstall 합니다.

Vite에서 default로 사용하는 moduleResolution 속성값인 bundler는 TypeScript 5.0 이상에서 추가된 값이기 때문에 tsconfig에서 moduleResolution: node로 변경합니다.

성공입니다!
혹시나 import React from ‘react’와 같은 구문에 계속해서 에러가 발생한다면 npx jest —clearCache로 캐시를 정리하거나 VSCode 에디터 자체를 재실행하는 것도 시도해 보시기 바랍니다.
나가며
여러 라이브러리와 패키지의 설정과 의존성을 살펴보면서 다음과 같은 세 가지를 주의해야겠다고 생각하였습니다.
첫째, 공식 문서에서 버전에 따른 사용법을 잘 확인하고 사용해야 한다.
둘째, 패키지를 설치할 때, 의존성 warning을 잘 확인하고, 설치하는 패키지 버전과 호환되도록 다른 패키지 버전을 업데이트하여 사용해야 한다.
셋째, 만약 계속해서 안 된다면 동일한 환경을 구축한 케이스를 보고 config를 비교해 본다.
이때까지 유사한 환경에서 작업을 하다 최근에 다른 환경에서 할 일이 생기면서 환경 구축에 관심을 가지고, 조금 더 깊게 이해해 볼 수 있는 유익한 시간이었습니다. 현재 진행하고 있는 TDD 스터디에서도 관련 내용을 공유하였는데, 다들 흥미롭게 들어주셨습니다. 스터디원분들이 다른 해결책도 제시해 주시고, 최근 회사에서도 Node 버전을 변경하면서 유사한 경험을 하였다고 공감해 주셔서 의미 있는 시간이었습니다. 앞으로도 테스트에 관심을 가지고 조금 더 안전한 코드를 작성할 수 있도록 노력해야겠습니다.
'FrontEnd > Test' 카테고리의 다른 글
프론트엔드 테스트 어떤 순서로, 무엇을 테스트하면 좋을까? (23) | 2024.03.31 |
---|---|
프론트엔드 Vite 프로젝트에 Vitest 적용하기 (1) | 2024.01.20 |
Vite, TypeScript, React Testing Library, Jest 설정하기 - (1) 각 파일 설정 이해하기 (1) | 2023.12.24 |
들어가며
[1편]에서 테스트 환경 구축 시 사용되는 패키지의 설정 파일을 살펴보았습니다. [2편]에서는 먼저 Vite, TypeScript, React Testing Library, Jest를 사용해 테스트 환경을 구축하고, 패키지 간 의존성과 설정 파일 문제로 발생했던 에러와 해결책을 공유하고자 합니다.
React 테스트 환경 구축하기(Vite, TS, Jest, React Testing Library)
Vite 프로젝트 설치
npm create vite@latest test-config -- --template react-ts
cd test-config
npm install
code . // vscode 열기
테스트 관련 패키지 설치
// 2-1 Jest 관련 패키지 설치
npm i -D jest jest-environment-jsdom ts-jest jest-transform-stub @types/jest
// 2-2 React Testing Library 관련 패키지 설치
npm i -D @testing-library/react @testing-library/jest-dom
Jest 관련 패키지 설치
npm i -D jest jest-environment-jsdom ts-jest jest-transform-stub @types/jest
- jest-environment-jsdom
- Jest v.28 이상부터 jsdom이 더 이상 기본 환경이 아님
- 따라서 해당 패키지를 설치하고 jest.config.js에 testEnvironment 추가해야 jsdom을 전역적으로 사용 가능
- ts-jest
- JavaScript만 지원하는 Jest가 TypeScript를 이해할 수 있도록 변환하는 역할.
- 사용하는 Jest와 호환되는 버전을 설치(중요)
- Jest에서 TypeScript를 사용하기 위해 @babel/preset-typescript를 사용할 수도 있지만, Babel의 역할은 TypeScript를 JavaScript로 트랜스파일링하는 것이기 때문에 TypeScript 컴파일러 tsc처럼 완전한 타입체크를 하지 않는다는 단점이 있다. 따라서 완전한 타입 체킹을 하려면 ts-jest를 사용하거나 tsc를 따로 실행해야 한다.
- @types/jest
- jest의 타입 패키지로, test, expect와 같은 Jest global API의 타이핑을 위해 필요. 아래와 같이 필요한 인자들을 알려준다.
- Jest 버전을 해당 패키지와 최대한 유사하게 맞춰야 한다. 예를 들어, 만약 Jest 27.4.0을 사용하고 있으면, @types/jest 27.4.x를 사용하는 것이 좋다.

- jest-transform-stub
- Jest는 JavaScript가 아닌 asset을 처리하지 않기 때문에 nonJS 파일(svg, css 등)을 import 할 때 해당 모듈로 에러 방지
- 환경 설정 글에서 자주 보이는 svg 처리를 위한 jest-svg-transformer는 Jest 29와는 호환되지 않으므로 주의
React Testing Library 관련 패키지 설치
npm i -D @testing-library/react @testing-library/jest-dom
- @testing-library/react
- 코어인 @testing-library/dom을 React에 맞춘 패키지
- @testing-library/jest-dom
- Jest 기본 matcher 이외 jest의 DOM에 특화된 matcher 패키지
jest-environment-jsdom과 @testing-library/jest-dom 둘 다 필요한가요?
jsdom과 jest-dom이 이름이 비슷해 헷갈릴 수 있을 것 같습니다.
jest-environment-jsdom은 브라우저 없이 테스트를 실행하기 위한 가상 DOM(jsdom) 패키지이고,
@testing-library/jest-dom은 toBeInTheDocument(), toBeChecked()와 같이 jsDOM 요소를 찾기 위한 matcher(jest-dom)입니다. 따라서 둘 다 설치하여 사용하면 됩니다.
Jest 설정 파일 작성
npm init jest@latest 또는 루트에 직접 jest.config.ts 또는. js 파일을 만들어 작성합니다.
// jest.config.json
import type { Config } from "jest";
const config: Config = {
testEnvironment: "jsdom", // 테스트 시 jsdom 환경 사용
preset: "ts-jest", // TypeScript 사용
transform: {
".+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$": "jest-transform-stub", // nonJS 파일 처리
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], // 모든 테스트 파일에 jest-dom import
testMatch: ["<rootDir>/**/?(*.)+(spec|test).[tj]s[x]"], // 검사할 파일 형식 정의
clearMocks: true, // 모든 테스트 실행 전 자동으로 mock 호출, 인스턴스, 컨텍스트, 결과값 클리어
collectCoverage: true, // 테스트 실행 동안 커버리지 정보 수집
coverageDirectory: "coverage", // 커버리지 파일 output 디렉토리
coverageProvider: "v8", // 커버리지 확인을 위해 사용할 프로바이더
};
export default config;
루트에 jest.setup.ts 파일을 작성합니다.
import "@testing-library/jest-dom";
Vite의 public 경로를 사용하기 위해 jest에 public alias를 설정합니다.
// jest.config.json
const config: Config = {
...
moduleNameMapper: {
"^@public/(.*)$": "<rootDir>/public/$1",
},
}
// App.tsx
...
import viteLogo from "@public/vite.svg";
function App() { ... }
export default App;
Vite에서는 public 폴더의 asset을 절대 경로로 접근할 수 있지만, Jest는 이를 읽지 못합니다. 따라서 jest.config에서 alias를 지정해 경로를 읽을 수 있도록 합니다. 사용된 정규표현식은 @public으로 시작하는 경로에서 / 이후의 (.*) 부분을 $1에 적용합니다.
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"esModuleInterop": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
- jsx 속성을 react-jsx를 react로 수정
- react: TypeScript가 JSX 생성 함수를 React.createElement로 추정하고, 사용되지 않은 import에 대해 warning을 제기 X
- react-jsx: TypeScript가 사용자 정의 React 변수를 제공할 것이라고 추정해 React를 파일에서 사용해야 함
- 따라서 TypeScript 환경에서 React를 import 하고 사용하지 않아도 테스트 에러가 나지 않도록 jsx: “react”로 지정
- esModuleInterop을 true로 설정
- esModuleInterop은 CommonJS와 ES modules를 모두 사용하는 환경에서 Typescript가 두 가지 다른 모듈 시스템에 모두 호환되도록 트랜스파일
- Jest와 같이 CommonJS를 사용하는 third-party 라이브러리를 사용할 때 필요
테스트 작성
루트에 테스트 폴더를 생성하고, 테스트를 작성합니다.
// src/App.tsx
import React from "react";
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
function App() {
const [count, setCount] = useState(0);
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>count is {count}</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
);
}
export default App;
// tests/App.test.tsx
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import App from "../src/App";
test("버튼 초기 상태 확인", () => {
render(<App />);
const counterButton = screen.getByRole("button", { name: /count/i });
expect(counterButton.textContent).toBe("count is 0");
});
test("버튼을 클릭하면 1이 증가한다.", () => {
render(<App />);
const counterButton = screen.getByRole("button", { name: /count/i });
fireEvent.click(counterButton);
expect(counterButton.textContent).toBe("count is 1");
});

테스트 성공!!
멀티 프로젝트 혹은 모노레포 테스트하기
지금까지 하나의 프론트엔드 프로젝트에서 테스트 환경을 구축하는 방법을 살펴보았습니다. 이제부터 여러 프로젝트가 있는 경우 테스트 환경을 구성해 보겠습니다. 이 경우는 크게 다르지 않지만, 패키지가 설정 파일을 찾고, 실행하는 방법을 이해해야 합니다.
프론트엔드와 백엔드 폴더가 하나의 프로젝트에 주어진다고 가정해보겠습니다. 루트에 있는 package.json에 프론트(frontend)와 백엔드(src)가 사용하는 패키지를 설치하고 공유합니다. 프론트엔드 폴더는 React 테스트 환경 구축하기에서 진행했던 구성과 동일합니다.

[1편]에서 살펴본 것처럼 각 패키지는 현재 작업 폴더(동작을 실행할 대상 폴더) 내에 설정 파일이 있는지 확인하고, 없으면 상위 폴더로 올라가면서 설정 파일을 찾습니다.
그렇다면 현재 작업하고 있는 폴더는 어떻게 알 수 있을까요? 이는 config 파일 설정이나 pacakage.json의 위치에 따라 다릅니다. 예를 들어, Jest는 package.json이 위치한 폴더 내의 모든 테스트 파일을 재귀적으로 실행합니다. 위의 경우 pacakge.json이 있는 루트 폴더에 있는 테스트, 즉 tests 폴더 내의 테스트 파일들을 실행할 것입니다. frontend내에도 test.ts[x] 파일을 가진 tests 폴더가 있다면 마찬가지로 실행합니다.
// pacakge.json
"scripts": {
"start": "nodemon server.js",
"test": "jest --watch",
"dev": "cd frontend && vite",
"build": "cd frontend && tsc && vite build",
"lint": "cd frnotend && eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
},
npm test로 테스트를 실행할 때 테스트에 TypeScript와 ESLint 설정을 적용하기 위해 루트에 tsconfig와 eslintrc 파일을 생성합니다.
frontend 내부의 tsconfig 파일을 루트로 옮기고, 일부 수정합니다.


tsconfig, eslintrc
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "node", // node 모듈로 수정
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"esModuleInterop": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
// 포함할 폴더 지정(프론트엔드만 TypeScript 사용)
"include": ["frontend/src", "frontend/test"],
"references": [{ "path": "./tsconfig.node.json" }]
}
// eslintrc.cjs
module.exports = {
root: true,
env: {
node: true,
jest: true,
},
parserOptions: {
ecmaVersion: 8,
},
extends: ['eslint:recommended'],
rules: {
'no-undef': 'off'
},
};
jest.config
이 부분이 가장 크게 차이가 나는데, projects 속성을 사용해 각 프로젝트에 따라 아래처럼 다른 설정을 해줍니다. 프론트엔드와 백엔드에서 실행할 테스트 파일 포맷을 지정하고, 필요한 설정들을 작성합니다.
// jest.config.ts
"use strict";
module.exports = {
projects: [
{
displayName: "frontend",
testEnvironment: "jest-environment-jsdom",
preset: "ts-jest",
setupFilesAfterEnv: ["<rootDir>/frontend/jest.setup.ts"],
transform: {
"^.+\\.tsx?$": "ts-jest",
".+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$": "jest-transform-stub",
},
},
{
displayName: "backend",
testMatch: ["<rootDir>/tests/**/?(*.)+(spec|test).[tj]s"],
},
],
};

테스트 성공입니다!
Jest 다운그레이드 버전 사용하기
현재(2024/1/6) 기준으로 Jest 최신 버전은 29.7입니다. 하지만 최신 버전을 사용하지 못하는 경우도 있을 텐데요. 예를 들어, 이전 버전 가운데 하나인 27.5.1 버전을 이미 사용하고 있다고 가정해 보겠습니다. 이때 유의해야 할 점은 다음과 같습니다.
- jest.config.json의 testEnvironment를 jest-environment-jsdom으로 설정
- ts-jest와 TypeScript의 호환 가능한 버전 설치. 예를 들어 jest 27.5.1의 경우 가장 유사한 버전인 ts-jest 27.1, TypeScript 4.2 사용
- 마찬가지로 @types/jest도 버전 맞춰주기
- jest-environment-jsdom 패키지 사용하고 있다면 uninstall. 그렇지 않으면 testEnvironment is undefined 에러 발생
React 테스트 환경 구축 에러 트러블슈팅
사실 위에서 소개한 내용은 해당 에러를 해결하면서 배운 내용입니다. 따라서 앞서 읽었던 것을 복습한다는 가벼운 마음으로 왜 그럴까? 생각해 보면서 읽어 내려가시면 좋을 것 같습니다.

처음으로 돌아가 시작해 봅시다. 이전 경우와 마찬가지로 frontend 폴더에 프론트엔드, src에 백엔드 코드가 있고, pacakge.json을 공유하고 있는 상황입니다. 프론트엔드, 백엔드를 한 프로젝트에서 실행하기 위해 처음 frontend에 vite-react 템플릿을 설치하고 tests 폴더를 추가하면 위와 같은 구조가 나옵니다. 이 상태에서 test에 App.test.tsx를 작성하고 테스트를 실행하면 다음과 같은 에러가 발생합니다.

tsconfig의 옵션인 esModuleInterop을 사용하라는 내용과 —jsx가 설정되지 않아 JSX를 사용할 수 없다는 내용입니다. 즉, tsconfig를 제대로 적용하고 있지 못합니다.

esModuleInterop과 jsx 설정을 변경하였는데도 동일한 에러가 발생합니다. 이는 tsconfig가 frontend 폴더 내에 있기 때문인데요. npm test를 실행했을 때 가장 상위 폴더인 루트에서부터 파일을 찾아가는데 tsconfig가 루트에 없기 때문에 설정을 적용하지 못한 것입니다.

이렇게 루트로 tsconfig를 꺼내고 나면 29 버전에서 테스트가 잘 동작할 것입니다. 여기에서 다운그레이드해보겠습니다.
yarn remove jest ts-jest && yarn add -D jest@27.5.1 ts-jest@27.1
그리고 테스트를 살펴보면 testEnvironmentOptions를 찾을 수 없다는 에러가 납니다.

Jest 버전이 28 이하이기 때문에 jest-environment-jsdom 패키지를 uninstall 합니다.

Vite에서 default로 사용하는 moduleResolution 속성값인 bundler는 TypeScript 5.0 이상에서 추가된 값이기 때문에 tsconfig에서 moduleResolution: node로 변경합니다.

성공입니다!
혹시나 import React from ‘react’와 같은 구문에 계속해서 에러가 발생한다면 npx jest —clearCache로 캐시를 정리하거나 VSCode 에디터 자체를 재실행하는 것도 시도해 보시기 바랍니다.
나가며
여러 라이브러리와 패키지의 설정과 의존성을 살펴보면서 다음과 같은 세 가지를 주의해야겠다고 생각하였습니다.
첫째, 공식 문서에서 버전에 따른 사용법을 잘 확인하고 사용해야 한다.
둘째, 패키지를 설치할 때, 의존성 warning을 잘 확인하고, 설치하는 패키지 버전과 호환되도록 다른 패키지 버전을 업데이트하여 사용해야 한다.
셋째, 만약 계속해서 안 된다면 동일한 환경을 구축한 케이스를 보고 config를 비교해 본다.
이때까지 유사한 환경에서 작업을 하다 최근에 다른 환경에서 할 일이 생기면서 환경 구축에 관심을 가지고, 조금 더 깊게 이해해 볼 수 있는 유익한 시간이었습니다. 현재 진행하고 있는 TDD 스터디에서도 관련 내용을 공유하였는데, 다들 흥미롭게 들어주셨습니다. 스터디원분들이 다른 해결책도 제시해 주시고, 최근 회사에서도 Node 버전을 변경하면서 유사한 경험을 하였다고 공감해 주셔서 의미 있는 시간이었습니다. 앞으로도 테스트에 관심을 가지고 조금 더 안전한 코드를 작성할 수 있도록 노력해야겠습니다.
'FrontEnd > Test' 카테고리의 다른 글
프론트엔드 테스트 어떤 순서로, 무엇을 테스트하면 좋을까? (23) | 2024.03.31 |
---|---|
프론트엔드 Vite 프로젝트에 Vitest 적용하기 (1) | 2024.01.20 |
Vite, TypeScript, React Testing Library, Jest 설정하기 - (1) 각 파일 설정 이해하기 (1) | 2023.12.24 |