이번 포스트에서는 TypeScript 단위테스트에서 작성해보려고한다!
지금까지 개발을 할 때 코드를 작성하고 Postman으로 직접 테스트하면서 오류를 찾았었다.
테스트 코드의 중요성을 못느끼고 살았는데 TypeScript를 공부하면서 테스트 코드 작성도 같이 공부해보려고 한다!
테스트 코드를 작성하면서 느낀점은 기존에 작성했던 코드를 수정할 일이 생겼을 때 직접 API를 호출해서 테스트를 안하고 테스트 코드를 실행하기만 하면 테스트가 된다는 점에서 테스트 코드 작성의 중요성을 느꼈다!
테스트 코드의 종류로는 단위 테스트, 통합 테스트 등 여러가지가 있는데 나는 일단 단위 테스트 먼저 정리해보려고 한다.
나중에 통합 테스트(Integration Test), 부하 테스트 / 스트레스 테스트(Load / Stress Test) 이렇게 정리해보려고 한다!
단위 테스트는 프로그램의 가장 작은 단위인 함수나 메서드가 예상대로 동작하는지 검증하는 테스트다
쉽게 말하면, "이 함수가 입력을 받았을 때, 기대한 결과를 내보내는가?"를 자동으로 체크하는 코드!
// 함수
function add(a: number, b: number): number {
return a + b;
}
// 단위 테스트 (Jest)
test('add 함수는 두 수의 합을 반환해야 한다.', () => {
expect(add(2, 5)).toBe(5);
});
Jest 테스트 프레임워크를 활용한 테스트 환경을 설정할 것이다!
npm install --save-dev jest ts-jest @types/jest
// jest.config.js 생성
npx ts-jest config:init
나의 jest.config.js의 초기 설정은 이렇다!
/** @type {import('ts-jest').JestConfigWithTsJest} **/
// ts-jest를 사용하는 Jest 설정임을 명시하는 타입 선언 (자동 완성 및 타입 체크 지원)
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node', // 테스트 실행 환경을 node로 설정
roots: ['<rootDir>/src'], // root directory 설정
testMatch: [ // 테스트 파일 경로 패턴 지정
'**/tests/**/*.test.ts'
],
transform: { // .ts 또는 .tsx 파일을 ts-jest를 이용해 반환
"^.+\.tsx?$": ['ts-jest', {}],
},
// 테스트 커버리지 리포트를 생성
collectCoverage: true,
converageDirectory: 'coverage/',
// Jest가 모듈을 찾을 때 참조할 디렉토리들
moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: {
"^@/(.*)$": '<rootDir>/src/$1',
"^@tests/(.*)$": '<rootDir>/tests/$1',
...
}
};
초기 설정 속성
옵션 설명 testEnvironment테스트가 실행될 환경. 보통 "node"또는"jsdom"사용.transform파일을 테스트 전에 어떻게 변환할지 설정. TypeScript라면 ts-jest사용.testMatch어떤 파일을 테스트로 인식할지 glob 패턴으로 지정. testRegextestMatch대신 정규식으로 테스트 파일 경로 지정 가능.collectCoveragetrue로 설정하면 테스트 커버리지 리포트를 생성함.coverageDirectory커버리지 리포트를 저장할 디렉토리 이름 설정. 기본값은 "coverage".coveragePathIgnorePatterns커버리지 측정에서 제외할 경로 목록. moduleNameMapper경로 별칭(alias) 설정할 때 사용. 예: @/→src/setupFiles테스트 실행 전에 설정할 스크립트 목록. 예: 환경 변수 설정 setupFilesAfterEnv각 테스트 환경 설정 후 실행할 스크립트 (예: jest-extended,@testing-library/jest-dom)moduleFileExtensionsJest가 인식할 파일 확장자 목록. 기본값: ["js", "json", "jsx", "ts", "tsx", "node"]rootsJest가 테스트를 찾을 디렉토리 목록. 기본값은 ["<rootDir>"].globals전역 설정값 지정. 예: ts-jest관련 설정verbosetrue로 설정하면 테스트 실행 결과를 더 자세히 출력함.bail실패한 테스트가 있으면 즉시 중단할지 여부. 기본은 false.jest.config.js 파일 초기 설정을 할 때 조금 애먹었던건 tsConfig.js 설정에서 paths로 설정해뒀던 경로들을 jest.config.js에도 설정을 해야한다는 거였다!
설정을 안하면 별칭으로 import하는 클래스들을 찾아오지 못한다..ㅎ
보통 단위 테스트용 데이터를 모아두는 디렉토리로 __mock__/, factory/, fixtures/를 사용한다고 한다.
폴더 구조 예시를 보여주겠다!
project-root/
├── src/
│
├── tests/ ← 테스트 전용 디렉토리
│ ├── unit/ ← 단위 테스트
│ │ ├── utils/
│ │ │ └── utils.test.ts
│ │ └── services/
│ │ └── post.service.test.ts
│ │
│ ├── integration/ ← 통합 테스트
│ │ └── user-flow.test.ts
│ │
│ ├── __mocks__/ ← 모킹 객체/모듈
│ │ ├── axios.ts ← 예: axios 모킹
│ │ └── fs.ts
│ │
│ ├── factory/ ← 테스트용 객체 생성 함수
│ │ └── userFactory.ts
│ │
│ ├── fixtures/ ← 고정된 더미 데이터 (JSON 등)
│ │ ├── sample-user.json
│ │ └── config-fixture.ts
│ │
│ ├── setupTests.ts ← 테스트 환경 초기 설정 (예: jest setup)
│ └── jest.global.d.ts ← 전역 타입 정의 (필요시)
│
├── jest.config.ts
├── tsconfig.json
└── package.json
📁 디렉터리 설명
- unit - 컴포넌트나 서비스 등 모듈 단위 테스트
- intergration - 여러 모듈이 엮인 흐름 테스트
__mocks__- 모듈이나 라이브러리를 mocking할 때 사용 (axios, fs, i18n 등)- factory - 객체 생성 도우미 함수들 (동적 더미 객체 생성)
- fixtures - 고정된 더미 데이터 (예: JSON, config 등)
- setupTests.ts - 글로벌 테스트 설정 (ex: jest.useFakeTimers(), cleanup() )
- jest.global.d.ts - Jest 커스텀 matchers, 글로벌 변수 타입 정의
실제 데이터베이스에 접근하는 PostDao와 같은 DB 접근 객체는 jest.fn()을 사용해 mock으로 대체하여 테스트 한다.
jest.fn()은 함수를 mocking, jest.mock()은 모듈 자체를 mocking 하는 것이다.
describe()는 관련 테스트들을 그룹화하고, it() 또는 test()는 각각의 테스트 케이스를 작성하는 데 사용한다.
/**
* Post Service Unit Test 파일
*/
import 'reflect-metadata';
import { PostService } from '@/services/post.service';
import { S3FileStorageService } from "@/services/file.service";
import { PostDao } from "@/daos/post.dao";
import { describe } from "node:test";
// jest.mock: 특정 모듈 전체를 mock 처리할 때 사용
jest.mock('@/services/file.service');
describe('PostService', () => {
let postService: PostService;
let s3FileStorageService: jest.Mocked<S3FileStorageService>;
let postDao: jest.Mocked<PostDao>;
beforeEach(() => {
// S3FileStorageService를 mock instance로 생성
s3FileStorageService = {
deleteFiles: jest.fn(),
} as unknown as jest.Mocked<S3FileStorageService>;
// postDao mock 생성
postDao = {
findAll: jest.fn(), // 함수 mocking
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
} as unknown as jest.Mocked<PostDao>;
postService = new PostService(postDao, s3FileStorageService);
});
// describe: findAll 테스트 그룹화
describe('findAll()', () => {
// it: 테스트 케이스 작성(test로 작성해도 무방)
it("전체 게시글 조회에 성공하고 데이터를 반환한다.", async () => {
const mockPosts = [{id: 1, title: '테스트 게시글'}];
// postDao.findAll의 응답을 정한다.
postDao.findAll.mockResolvedValue({
success: true,
data: mockPosts
});
// postService.findAll() 실행
const result = await postService.findAll();
// 예상 응답과, 실제 응답이 같은지 비교한다.
expect(result).toEqual({ success: true, data: mockPosts });
expect(postDao.findAll).toHaveBeenCalled();
});
it("전체 게시글 조회 내역이 없어서 404 오류를 반환한다.", async () => {
postDao.findAll.mockResolvedValue({
success: false,
data: []
});
await expect(postService.findAll()).rejects.toThrow('게시글을 찾을 수 없습니다.');
expect(postDao.findAll).toHaveBeenCalled();
})
it("전체 게시글 조회 중 오류 발생으로 500 에러 반환", async () => {
postDao.findAll.mockResolvedValue({
success: false,
error: "전체 Post 조회 중 문제 발생"
});
await expect(postService.findAll()).rejects.toThrow('전체 Post 조회 중 문제 발생');
expect(postDao.findAll).toHaveBeenCalled();
})
});
});
npx jest를 직접 console에 입력해서 실행을 해도 되고, package.json scripts에 등록해서 사용해도 된다!
Scripts 추가 예시

npx jest 관련 옵션
--watchAll:
프로젝트의 모든 테스트를 감시하면서 실행한다.
(git 상태 무시하고 전체 실행)--watch:
Git의 변경된 파일들만 감시해서 테스트 실행
(예:git status로 modified만 파일들 중심)--coverage:
테스트 커버리지 리포트도 함께 출력