[Node.js] 심화 실습 (14-18) (Jest configs / CLI Options / scripts / Mock / 의존성주입DI / Unit Test)
이때까지 실습한 Layered Architecture에 Jest를 적용해보기 위해 아래의 순서로 설정함.
export default {
// 해당 패턴에 일치하는 경로가 존재할 경우 테스트를 하지 않고 넘어갑니다.
testPathIgnorePatterns: ['/node_modules/'],
// 테스트 실행 시 각 TestCase에 대한 출력을 해줍니다.
verbose: true,
};
{
...
"scripts": {
...
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --forceExit",
"test:silent": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --silent --forceExit",
"test:coverage": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --coverage --forceExit",
"test:unit": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest __tests__/unit --forceExit"
},
...
}
뒷 단의 silent, coverage 등은 특정 경로를 뜻함 (후술).기존의 repository 코드:
import { prisma } from '../utils/prisma/index.js';
export class PostsRepository {
findAllPosts = async () => {
// ORM인 Prisma에서 Posts 모델의 findMany 메서드를 사용해 데이터를 요청합니다.
const posts = await prisma.posts.findMany();
return posts;
};
의존성 주입 코드:
export class PostsRepository {
constructor(prisma) {
// 생성자에서 전달받은 Prisma 클라이언트의 의존성을 주입합니다.
this.prisma = prisma;
}
findAllPosts = async () => {
// ORM인 Prisma에서 Posts 모델의 findMany 메서드를 사용해 데이터를 요청합니다.
const posts = await this.prisma.posts.findMany();
return posts;
};
import express from 'express';
import { prisma } from '../utils/prisma/index.js';
import { PostsRepository } from '../repositories/posts.repository.js';
import { PostsService } from '../services/posts.service.js';
import { PostsController } from '../controllers/posts.controller.js';
const router = express.Router();
// 3계층의 의존성을 모두 주입합니다.
const postsRepository = new PostsRepository(prisma);
const postsService = new PostsService(postsRepository);
const postsController = new PostsController(postsService);
// __tests__/unit/posts.repository.unit.spec.js
import { jest } from '@jest/globals';
import { PostsRepository } from '../../../src/repositories/posts.repository';
// Prisma 클라이언트에서는 아래 5개의 메서드만 사용합니다.
let mockPrisma = {
posts: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
};
let postsRepository = new PostsRepository(mockPrisma);
describe('Posts Repository Unit Test', () => {
// 각 test가 실행되기 전에 실행됩니다.
beforeEach(() => {
jest.resetAllMocks(); // 모든 Mock을 초기화합니다.
});
test('findAllPosts Method', async () => {
// findMany Mock의 Return 값을 "findMany String"으로 설정합니다.
const mockReturn = 'findMany String';
mockPrisma.posts.findMany.mockReturnValue(mockReturn);
// postsRepository의 findAllPosts Method를 호출합니다.
const posts = await postsRepository.findAllPosts();
// prisma.posts의 findMany은 1번만 호출 되었습니다.
expect(postsRepository.prisma.posts.findMany).toHaveBeenCalledTimes(1);
// mockPrisma의 Return과 출력된 findMany Method의 값이 일치하는지 비교합니다.
expect(posts).toBe(mockReturn);
});
test('createPost Method', async () => {
// create Mock의 Return 값을 "create Return String"으로 설정합니다.
const mockReturn = 'create Return String';
mockPrisma.posts.create.mockReturnValue(mockReturn);
// createPost Method를 실행하기 위해 필요한 Params 입니다.
const createPostParams = {
nickname: 'createPostNickname',
password: 'createPostPassword',
title: 'createPostTitle',
content: 'createPostContent',
};
// postsRepository의 createPost Method를 실행합니다.
const createPostData = await postsRepository.createPost(
createPostParams.nickname,
createPostParams.password,
createPostParams.title,
createPostParams.content,
);
// createPostData는 prisma.posts의 create를 실행한 결과값을 바로 반환한 값인지 테스트합니다.
expect(createPostData).toBe(mockReturn);
// postsRepository의 createPost Method를 실행했을 때, prisma.posts의 create를 1번 실행합니다.
expect(mockPrisma.posts.create).toHaveBeenCalledTimes(1);
// postsRepository의 createPost Method를 실행했을 때, prisma.posts의 create를 아래와 같은 값으로 호출합니다.
expect(mockPrisma.posts.create).toHaveBeenCalledWith({
data: createPostParams,
});
});
});
Mock Functions: Mock은 특정 메서드나 함수를 Mocking하기 위해 사용됨. 즉, 테스트에서 시간 또는 비용이 많이들거나, 의존성이 높은 코드를 직접 실행하지 않고 호출 여부, 입력한 값의 일치 여부와 같은 정보를 실제로 실행한 것처럼 확인하기 위해 사용하는 가짜 객체인 것.
자료 출처: 스파르타코딩클럽