테스트 코드를 구현하려했을 때 가장 많이 작업할 계층은 이 Service Layer!
실제로 프로젝트를 진행할 때 Service Layer는 비즈니스 로직, 에러 핸들링과 같은 핵심적인 작업을 하게 됩니다. 그로인해 다양한 계층 중에서 가장 많은 코드 분량을 자랑하고, 복잡한 코드 구성을 가지게 될 것!
Service Layer의 경우 Repository Layer를 하위 계층으로 가지고 있습니다.
즉, Service Layer의 단위 테스트 (UnitTest)를 구현하기 위해서는 Repository 계층을 Mocking하여 원하는 비즈니스 로직이 정상적으로 동작하는지 확인
__tests__
폴더 하위에 unit폴더를 생성하고,posts.service.unit.spec.js
파일을 만들어 진행- Service Layer의 단위 테스트는 findAllPost, deletePost 2개의 Method를 테스트
- repository를 Mocking
- Service에서는 에러를 고의로 발생시켜 정상적으로 에러가 발생하는지 또한 검증
const PostService = require("../../services/posts.service.js");
let mockPostsRepository = {
findAllPost: jest.fn(),
findPostById: jest.fn(),
createPost: jest.fn(),
updatePost: jest.fn(),
deletePost: jest.fn(),
};
let postService = new PostService();
// postService의 Repository를 Mock Repository로 변경합니다.
postService.postRepository = mockPostsRepository;
describe("Layered Architecture Pattern Posts Service Unit Test", () => {
// 각 test가 실행되기 전에 실행됩니다.
beforeEach(() => {
jest.resetAllMocks(); // 모든 Mock을 초기화합니다.
});
test("Posts Service findAllPost Method", async () => {
const findAllPostReturnValue = [
{
postId: 1,
nickname: "nickname_1",
title: "title_1",
createdAt: new Date("11 October 2022 00:00"),
updatedAt: new Date("11 October 2022 00:00"),
},
{
postId: 2,
nickname: "nickname_2",
title: "title_2",
createdAt: new Date("12 October 2022 00:00"),
updatedAt: new Date("12 October 2022 00:00"),
},
];
mockPostsRepository.findAllPost = jest.fn(() => {
return findAllPostReturnValue;
});
const allPost = await postService.findAllPost();
// 1. 결과가 sort된 결과를 제대로 가져오는지 검증
expect(allPost).toEqual(
findAllPostReturnValue.sort((a, b) => {
return b.createdAt - a.createdAt;
})
);
// 2. 한 번 호출되는지 검증
expect(mockPostsRepository.findAllPost).toHaveBeenCalledTimes(1);
});
test("Posts Service deletePost Method By Success", async () => {
const findPostByIdReturnValue = {
postId: 1,
nickname: "nickname_1",
title: "title_1",
content: "content_1",
createdAt: new Date("11 October 2022 00:00"),
updatedAt: new Date("11 October 2022 00:00"),
};
mockPostsRepository.findPostById = jest.fn(() => {
return findPostByIdReturnValue;
});
const deletePost = await postService.deletePost(1, "0000");
// 1. 1번 호출, 입력받는 인자는 postId
expect(mockPostsRepository.findPostById).toHaveBeenCalledTimes(1);
expect(mockPostsRepository.findPostById).toHaveBeenCalledWith(1);
// 2. postId, password, deletePost Method가 호출
expect(mockPostsRepository.deletePost).toHaveBeenCalledTimes(1);
expect(mockPostsRepository.deletePost).toHaveBeenCalledWith(1, "0000");
// 3. return값이 findPostById의 반환된 결과와 일치
expect(deletePost).toMatchObject({
postId: findPostByIdReturnValue.postId,
nickname: findPostByIdReturnValue.nickname,
title: findPostByIdReturnValue.title,
content: findPostByIdReturnValue.content,
createdAt: findPostByIdReturnValue.createdAt,
updatedAt: findPostByIdReturnValue.updatedAt,
});
});
test("Posts Service deletePost Method By Not Found Post Error", async () => {
const findPostByIdReturnValue = null;
mockPostsRepository.findPostById = jest.fn(() => {
return findPostByIdReturnValue;
});
try {
const deletePost = await postService.deletePost(90, "0000");
} catch (error) {
// 1. postId 입력한 findPostById Method 1번 호출
expect(mockPostsRepository.findPostById).toHaveBeenCalledTimes(1);
expect(mockPostsRepository.findPostById).toHaveBeenCalledWith(90);
// 2. return 된 findPostById의 결과가 존재하지 않을 때 에러발생
expect(error.message).toEqual("Post doesn't exist");
}
});
});
expect
를 이용해 Mock의 호출 횟수, 호출 인자만을 검증했던 성공 케이스와 다르게,
에러 케이스의 경우try catch
를 이용해 에러 핸들링을 하고, 에러가 발생한 경우 테스트 코드가 동작하도록 구현하였습니다.
expect(error.message).toEqual("Post doesn't exist");
와 같이 에러메시지를 검증하여 원하는 에러가 정상적으로 발생하였는지까지 검증 완료