Class
**Layered Achitecture Pattern**
Unit Test
jest
❗ 시간 관계상 모든 부분을 라이브 코딩으로 진행하긴 어렵습니다! 그러니 이미 작성된 코드로 차근이 출발 해 봅시다! 속도가 빠를 수 있으니, 여러분들 이해가 안될때 멈춰 외쳐주시고, 반복하면서 가봅시다![ 특강 목표 ]
1. 왜 테스트를 해야하는가?
2. Layer 나누기 전의 테스트 코드 작성하기
3. Layer 나눈 후의 테스트 코드
4. jest 테스트 코드 작성하는 방법
5. 여러분 성공 케이스 만큼 실패에 대한 검증도 중요합니다.
최대한 simple 한 코드 덩어리 기반으로,
“Node 심화 1주차 강의” 에서 했던 것을 위주로 다시 보는 형태로 진행할 예정입니다.
app.get("/ping", (req, res, next) => {
return res.status(200).json({ message: "ok" });
});
Insomnia
(HTTP API TEST TOOL), Thunder Client
, 뭐 브라우저 등등, 직접 요청을 하셨을 겁니다.app.post("/user", (req, res, next) => {
try {
const { email, password, passwordConfirm, name, age, gender } = req.body;
if (!email || !password || !passwordConfirm || !name || !gender)
throw new Error('필수 값이 입력되지 않았습니다.');
if (password !== passwordConfirm) throw new Error('비밀번호가 일치하지 않습니다.');
if (password.length < 6) throw new Error('비밀번호는 6자 이상이어야 합니다.');
// 유저 생성하기 코드 생략...
return res.status(201).json({ user });
} catch (err) {
next(err);
}
};
email
, password
, passwordConfirm
, name
, age
, gender
데이터를 client 에게 받아야 합니다.app.post("/user", (req, res, next) => {
try {
const { email, password, passwordConfirm, name, age, gender } = req.body;
if (!email || !password || !passwordConfirm || !name || !gender)
...생략...
}
};
app.post("/post", (req, res, next) => {
try {
const { content, title } = req.body;
if (!content || !title)
...생략...
}
};
app.post("/comment", (req, res, next) => {
try {
const { content } = req.body;
if (!content)
...생략...
}
};
app.post("/profile", (req, res, next) => {
try {
const { nickname, profileImg } = req.body;
if (!nickname || !profileImg)
...생략...
}
};
...dammn....
project-root/
├── node_modules/ # NPM 패키지 디렉토리
├── src/
│ ├── controllers/ # 컨트롤러 파일 디렉토리
│ │ ├── model1Controller.js
│ │ ├── model2Controller.js
│ │ ├── model3Controller.js
│ │ ├── model4Controller.js
│ │ ├── model5Controller.js
│ │ ├── model6Controller.js
│ │ ├── model7Controller.js
│ │ ├── model8Controller.js
│ │ ├── model9Controller.js
│ │ ... 생략 ...
│ ├── models/ # 모델 파일 디렉토리
│ │ ├── model1.js
│ │ ├── model2.js
│ │ ├── model3.js
│ │ ├── model4.js
│ │ ├── model5.js
│ │ ├── model6.js
│ │ ├── model7.js
│ │ ├── model8.js
│ │ ├── ***model9.js***
│ │ ... 생략 ...
│ ├── repositories/ # 레포지토리 파일 디렉토리
│ │ ├── model1Repository.js
│ │ ├── model2Repository.js
│ │ ├── model3Repository.js
│ │ ├── model4Repository.js
│ │ ├── model5Repository.js
│ │ ├── model6Repository.js
│ │ ├── model7Repository.js
│ │ ├── model8Repository.js
│ │ ├── model9Repository.js
│ │ ... 생략 ...
│ └── services/ # 서비스 파일 디렉토리
│ ├── model1Service.js
│ ├── model2Service.js
│ ├── model3Service.js
│ ├── model4Service.js
│ ├── model5Service.js
│ ├── model6Service.js
│ ├── model7Service.js
│ ├── model8Service.js
│ ├── model9Service.js
│ ... 생략 ...
├── app.js # 애플리케이션 메인 파일
├── package.json # 프로젝트
model9
와 관련된 “속성” 을 하나 바꿔야 하는 임무를 받았습니다. model9
를 쳐봅니다.model9Repository.js
에서 model9
를 쓰는 코드를 발견했다고 해보죠!model3Service.js
, model4Service.js
, model9Service.js
에서 모두 사용하는 것 같아요!express
ps) 여러분들 현업 코드가 궁금하다면, github 의 open-source 들의 코드를 많이 살펴보세요!import express from "express";
import bodyParser from "body-parser";
const app = express();
const PORT = 3000;
app.use(bodyParser.json());
app.use(express.json());
export class UserManagement {
constructor() {
this.users = []; // 사용자 데이터를 저장하는 배열
}
// 사용자 생성
createUser = async (req, res) => {
const { id, name } = req.body;
if (!id || !name) {
res.status(422).json({ error: "ID와 이름은 필수입니다." });
return;
}
const user = { id, name };
this.users.push(user);
res.status(201).json(user);
}
// 모든 사용자 조회
findAllUsers = async (req, res) => {
res.status(200).json(this.users);
}
// ID로 사용자 조회
findUserById = async (req, res) => {
const userId = req.params.id;
const user = this.users.find(user => user.id === userId);
if (user) {
res.status(200).json(user);
} else {
res.status(404).json({ error: "User not found" });
}
}
// 사용자 업데이트
updateUser = async (req, res) => {
const userId = req.params.id;
const { id, name } = req.body;
const userIndex = this.users.findIndex(user => user.id === userId);
if (userIndex !== -1) {
this.users[userIndex] = { id, name };
res.status(200).json(this.users[userIndex]);
} else {
res.status(404).json({ error: "User not found" });
}
}
// 사용자 삭제
deleteUser = async (req, res) => {
const userId = req.params.id;
const userIndex = this.users.findIndex(user => user.id === userId);
if (userIndex !== -1) {
this.users.splice(userIndex, 1);
res.status(204).json({});
} else {
res.status(404).json({ error: "User not found" });
}
}
}
const userManagement = new UserManagement();
app.get("/users", async (req, res) => await userManagement.findAllUsers(req, res));
app.post("/user", async (req, res) => await userManagement.createUser(req, res));
app.get("/user/:id", async (req, res) => await userManagement.findUserById(req, res));
app.put("/user/:id", async (req, res) => await userManagement.updateUser(req, res));
app.delete("/user/:id", async (req, res) => await userManagement.deleteUser(req, res));
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
package.json
- 심화 1주차 내용과 완전 동일합니다.{
"name": "before",
"version": "1.0.0",
"main": "app.js",
"license": "MIT",
"type": "module",
"scripts": {
"start": "nodemon app.js",
"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"
},
"dependencies": {
"express": "^4.18.2",
"nodemon": "^3.0.3"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"cross-env": "^7.0.3",
"jest": "^29.7.0"
}
}
1차 테스트 코드 고찰하기
import { jest } from "@jest/globals";
import { UserManagement } from "../../app.js";
describe("UserManagement", () => {
// 사전에 필요한 mock 데이터 세팅
let mockReq;
let mockRes;
let userManagement;
beforeEach(() => {
jest.resetAllMocks();
// 각 테스트 실행 전에 mock 객체를 초기화합니다.
mockReq = { params: {}, body: {} };
mockRes = {
status: jest.fn().mockReturnThis(), // status 메서드가 this를 반환하도록 수정
json: jest.fn().mockReturnThis(), // json 메서드도 체이닝을 위해 this를 반환하도록 수정
cookie: jest.fn().mockReturnThis(),
};
userManagement = new UserManagement();
});
it("should create a user", async () => {
mockReq.body = { id: "1", name: "John Doe" };
await userManagement.createUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
expect(mockRes.json).toHaveBeenCalledWith(mockReq.body);
expect(userManagement.users).toHaveLength(1);
expect(userManagement.users[0]).toEqual(mockReq.body);
});
it("should return all users", async () => {
const mockUsers = [{ id: "1", name: "John Doe" }, { id: "2", name: "Jane Doe" }];
userManagement.users = mockUsers;
await userManagement.findAllUsers(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith(mockUsers);
});
it("should find a user by ID", async () => {
const user = { id: "1", name: "John Doe" };
userManagement.users.push(user);
mockReq.params.id = "1";
await userManagement.findUserById(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith(user);
expect(userManagement.users[0].name).toBe("John Doe");
});
it("should return 404 if user not found when finding a user", async () => {
mockReq.params.id = "2";
await userManagement.findUserById(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(404);
expect(mockRes.json).toHaveBeenCalledWith({ error: "User not found" });
});
it("should update a user", async () => {
const user = { id: "1", name: "John Doe" };
userManagement.users.push(user);
mockReq.params.id = "1";
mockReq.body = { id: "1", name: "John Updated" };
await userManagement.updateUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith(mockReq.body);
expect(userManagement.users[0].name).toBe("John Updated");
});
it("should delete a user", async () => {
const user = { id: "1", name: "John Doe" };
userManagement.users.push(user);
mockReq.params.id = "1";
await userManagement.deleteUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(204);
expect(userManagement.users).toHaveLength(0);
});
});
1차 테스트 코드의 결과와 테스트 커버러지 체크
2차 테스트 코드 작성하기, 업데이트 하기
...생략...
it("should return 404 if user not found when updating a user", async () => {
mockReq.params.id = "2";
await userManagement.updateUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(404);
expect(mockRes.json).toHaveBeenCalledWith({ error: "User not found" });
});
it("should return 404 if user not found when deleting a user", async () => {
mockReq.params.id = "2";
await userManagement.deleteUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(404);
expect(mockRes.json).toHaveBeenCalledWith({ error: "User not found" });
});
2차 테스트 코드의 결과와 테스트 커버러지 체크
File | Statements | Branches | Functions | Lines |
---|---|---|---|---|
테스트 커버리지가 측정된 각 파일의 경로와 이름 | 프로그램에서 실행 가능한 각각의 명령문을 의미 | 전체 실행 가능한 문장 중 테스트를 통해 실행된 문장의 비율 | 코드 내의 조건문(예: if, switch)에서 여러 경로(분기) 중 얼마나 많은 경로가 테스트를 통해 실행되었는지 분기 커버리지는 코드 내의 모든 가능한 경로가 테스트를 통해 실행되었는지를 보여주는 지표 | 테스트 대상인 각 함수 또는 메서드의 커버리지를 나타냄.함수 커버리지는 전체 정의된 함수 중 테스트를 통해 실행된 함수의 비율 |
ps) 아래는 “이런게 있다~” 정도로만 이해하시면 됩니다.
테스트-후 개발(Test-After Development)
테스트-주도 개발(Test-Driven Development, TDD)
가장 먼저 느껴지는게 코드의 재사용성 및 확장성 문제
author
를 찾아야 한다.그러면 테스트 코드는 괜찮은가? → “테스트의 명확성 부족”
it("should create a user", async () => {
mockReq.body = { id: "1", name: "John Doe" };
await userManagement.createUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
expect(mockRes.json).toHaveBeenCalledWith(mockReq.body);
expect(userManagement.users).toHaveLength(1);
expect(userManagement.users[0]).toEqual(mockReq.body);
});
그 외, “비즈니스 로직”과 “프레임워크의 결합”
[ 아래는 여러분들이 이미 학습한 “1주차 “ 부분을 그대로 발췌한 내용입니다. ]
Repository
export class UserRepository {
constructor() {
this.users = []; // 사용자 데이터를 저장하는 배열
}
// 사용자 생성
createUser = async (user) => {
this.users.push(user);
return user;
}
// 모든 사용자 조회
findAllUsers = async () => {
return this.users;
}
// ID로 사용자 조회
findUserById = async (userId) => {
return this.users.find(user => user.id === userId);
}
// 사용자 업데이트
updateUser = async (userId, id, name) => {
const userIndex = this.users.findIndex(user => user.id === userId);
if (userIndex !== -1) {
this.users[userIndex] = { id, name };
return this.users[userIndex];
}
return null;
}
// 사용자 삭제
deleteUser = async (userId) => {
const userIndex = this.users.findIndex(user => user.id === userId);
if (userIndex !== -1) {
this.users.splice(userIndex, 1);
return true;
}
return false;
}
}
Service
export class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
// 새 사용자 생성
createUser = async (id, name) => {
const user = { id, name };
return await this.userRepository.createUser(user);
}
// 모든 사용자 조회
getAllUsers = async () => {
return await this.userRepository.findAllUsers();
}
// ID로 사용자 조회
getUserById = async (userId) => {
return await this.userRepository.findUserById(userId);
}
// 사용자 정보 업데이트
updateUser = async (userId, id, name) => {
const targetUser = await this.userRepository.findUserById(userId);
if (targetUser) {
return await this.userRepository.updateUser(userId, id, name);
}
return null;
}
// 사용자 삭제
removeUser = async (userId) => {
const targetUser = await this.userRepository.findUserById(userId);
if (targetUser) {
return await this.userRepository.deleteUser(userId);
}
return false;
}
}
Controller
export class UserController {
constructor(userService) {
this.userService = userService;
}
// 사용자 생성
createUser = async (req, res) => {
try {
const { id, name } = req.body;
if (!id || !name) {
throw new Error("ID와 이름은 필수입니다.");
}
const user = await this.userService.createUser(id, name);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ error: error.message });
}
}
// 모든 사용자 조회
getAllUsers = async (req, res) => {
const users = await this.userService.getAllUsers();
res.json(users);
}
// ID로 사용자 조회
getUserById = async (req, res) => {
try {
const userId = req.params.id;
const user = await this.userService.getUserById(userId);
if (user) {
res.json(user);
}
else {
throw new Error("User not found");
}
} catch (error) {
res.status(400).json({ error: error.message });
}
}
// 사용자 정보 업데이트
updateUser = async (req, res) => {
try {
const userId = req.params.id;
const { id, name } = req.body;
if (!id || !name) {
throw new Error("업데이트 할 내용이 없습니다.");
}
const updatedUser = await this.userService.updateUser(userId, id, name);
if (updatedUser) {
res.json(updatedUser);
} else {
throw new Error("User not found");
}
} catch (error) {
res.status(400).json({ error: error.message });
}
}
// 사용자 삭제
deleteUser = async (req, res) => {
try {
const userId = req.params.id;
const isDeleted = await this.userService.removeUser(userId);
if (isDeleted) {
res.status(204).json({});
} else {
throw new Error("User not found");
}
} catch (error) {
res.status(400).json({ error: error.message });
}
}
}
app.js
import express from "express";
import bodyParser from "body-parser";
import { UserRepository } from "./user.repository.js";
import { UserService } from "./user.service.js";
import { UserController } from "./user.controller.js";
const app = express();
const PORT = 3000;
app.use(bodyParser.json());
app.use(express.json());
const userRepository = new UserRepository();
const userService = new UserService(userRepository);
const userController = new UserController(userService);
// 사용자 관련 라우트 설정
app.get("/users", async (req, res) => await userController.getAllUsers(req, res));
app.post("/user", async (req, res) => await userController.createUser(req, res));
app.get("/user/:id", async (req, res) => await userController.getUserById(req, res));
app.put("/user/:id", async (req, res) => await userController.updateUser(req, res));
app.delete("/user/:id", async (req, res) => await userController.deleteUser(req, res));
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
자 여기서 재미있는 점이 있습니다.
지금까지 DBMS 를 사용하고 있지 않죠.
더욱이 프레임 워크를 바꿀 수 있습니다.
앞으로 배울 nest.js 말고, koa 라는 유명한 framework 도 있습니다.
어디만 바꾸면 될까요?
app.js
만 바꾸면 프레임워크도 바꿀 수 있습니다.
[ 계층 나누기 전의 문제점 ] 을 다시 고찰해봅시다.
그래도 여전히 API 가 수백개라면 “검증” 하는 데 있어서, 힘든 것은 비슷합니다!
그래서 테스트 코드가 필요합니다.
이와 같이 테스트 코드의 필요성과 3Layer 패턴의 필요성의 관점은 약간 다릅니다.
이 형태, 이 흐름은 javascript 라는 언어에 국한되는게 아닙니다.
C, C++, Java, Python … etc 언어는 “도구” 입니다.
Repository
import { UserRepository } from "../../user.repository.js";
describe("UserRepository", () => {
let userRepository;
beforeEach(() => {
userRepository = new UserRepository();
});
it("should create and find a user", async () => {
const user = { id: "1", name: "Test User" };
await userRepository.createUser(user);
const foundUser = await userRepository.findUserById(user.id);
expect(foundUser).toEqual(user);
});
it("should return all users", async () => {
const user1 = { id: "1", name: "Test User 1" };
const user2 = { id: "2", name: "Test User 2" };
await userRepository.createUser(user1);
await userRepository.createUser(user2);
const users = await userRepository.findAllUsers();
expect(users).toEqual(expect.arrayContaining([user1, user2]));
expect(users.length).toBe(2);
});
it("should update a user", async () => {
const user = { id: "1", name: "Original Name" };
await userRepository.createUser(user);
const updatedName = "Updated Name";
const updatedUser = await userRepository.updateUser(user.id, user.id, updatedName);
expect(updatedUser).toEqual({ id: user.id, name: updatedName });
const foundUser = await userRepository.findUserById(user.id);
expect(foundUser.name).toBe(updatedName);
});
it("should delete a user", async () => {
const user = { id: "1", name: "Test User" };
await userRepository.createUser(user);
const isDeleted = await userRepository.deleteUser(user.id);
expect(isDeleted).toBe(true);
expect(await userRepository.findUserById(user.id)).toBeUndefined();
});
it("should return null when updating a non-existent user", async () => {
const updateResult = await userRepository.updateUser("non-existent", "non-existent", "Non Existent");
expect(updateResult).toBeNull();
});
it("should return false when deleting a non-existent user", async () => {
const deleteResult = await userRepository.deleteUser("non-existent");
expect(deleteResult).toBe(false);
});
});
Service
import { jest } from "@jest/globals";
import { UserService } from "../../user.service.js";
describe("UserService", () => {
let mockUserRepository;
let userService;
beforeEach(() => {
jest.resetAllMocks();
mockUserRepository = {
createUser: jest.fn(),
findAllUsers: jest.fn(),
findUserById: jest.fn(),
updateUser: jest.fn(),
deleteUser: jest.fn(),
};
userService = new UserService(mockUserRepository);
});
it("should create a user", async () => {
const user = { id: "1", name: "Test User" };
mockUserRepository.createUser.mockResolvedValue(user);
const result = await userService.createUser(user.id, user.name);
expect(mockUserRepository.createUser).toHaveBeenCalledWith(user);
expect(result).toEqual(user);
});
it("should get all users", async () => {
const users = [
{ id: "1", name: "Test User 1" },
{ id: "2", name: "Test User 2" }
];
mockUserRepository.findAllUsers.mockResolvedValue(users);
const result = await userService.getAllUsers();
expect(mockUserRepository.findAllUsers).toHaveBeenCalled();
expect(result).toEqual(users);
});
it("should get a user by ID", async () => {
const user = { id: "1", name: "Test User" };
mockUserRepository.findUserById.mockResolvedValue(user);
const result = await userService.getUserById(user.id);
expect(mockUserRepository.findUserById).toHaveBeenCalledWith(user.id);
expect(result).toEqual(user);
});
it("should return null if user not found on get by ID", async () => {
mockUserRepository.findUserById.mockResolvedValue(null);
const result = await userService.getUserById("non-existent");
expect(result).toBeNull();
});
it("should update a user", async () => {
const user = { id: "1", name: "Updated User" };
mockUserRepository.findUserById.mockResolvedValue(user);
mockUserRepository.updateUser.mockResolvedValue(user);
const result = await userService.updateUser(user.id, user.id, user.name);
expect(mockUserRepository.updateUser).toHaveBeenCalledWith(user.id, user.id, user.name);
expect(result).toEqual(user);
});
it("should return null when trying to update a non-existent user", async () => {
mockUserRepository.findUserById.mockResolvedValue(null);
const result = await userService.updateUser("non-existent", "non-existent", "Non Existent");
expect(result).toBeNull();
});
it("should delete a user", async () => {
mockUserRepository.findUserById.mockResolvedValue(true); // Assuming the user exists
mockUserRepository.deleteUser.mockResolvedValue(true);
const result = await userService.removeUser("1");
expect(mockUserRepository.deleteUser).toHaveBeenCalledWith("1");
expect(result).toBe(true);
});
it("should return false when trying to delete a non-existent user", async () => {
mockUserRepository.findUserById.mockResolvedValue(null);
const result = await userService.removeUser("non-existent");
expect(result).toBe(false);
});
});
Controller
import { jest } from "@jest/globals";
import { UserController } from "../../user.controller";
describe("UserController", () => {
let mockUserService;
let userController;
let mockReq, mockRes;
beforeEach(() => {
jest.resetAllMocks();
mockUserService = {
createUser: jest.fn(),
getAllUsers: jest.fn(),
getUserById: jest.fn(),
updateUser: jest.fn(),
removeUser: jest.fn(),
};
mockReq = { params: {}, body: {} };
mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis(),
cookie: jest.fn().mockReturnThis(),
};
userController = new UserController(mockUserService);
});
it("should create a user and return 201 status", async () => {
const user = { id: "1", name: "Test User" };
mockReq.body = user;
mockUserService.createUser.mockResolvedValue(user);
await userController.createUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(201);
expect(mockRes.json).toHaveBeenCalledWith(user);
expect(mockUserService.createUser).toHaveBeenCalledWith(user.id, user.name);
});
it("should return all users", async () => {
const users = [{ id: "1", name: "Test User 1" }, { id: "2", name: "Test User 2" }];
mockUserService.getAllUsers.mockResolvedValue(users);
await userController.getAllUsers(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith(users);
expect(mockUserService.getAllUsers).toHaveBeenCalled();
});
it("should return a user by ID", async () => {
const user = { id: "1", name: "Test User" };
mockReq.params.id = user.id;
mockUserService.getUserById.mockResolvedValue(user);
await userController.getUserById(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith(user);
expect(mockUserService.getUserById).toHaveBeenCalledWith(user.id);
});
it("should return 400 if user not found by ID", async () => {
mockReq.params.id = "non-existent";
mockUserService.getUserById.mockResolvedValue(null);
await userController.getUserById(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({ error: "User not found" });
});
it("should update a user", async () => {
const updatedUser = { id: "1", name: "Updated Name" };
mockReq.params.id = updatedUser.id;
mockReq.body = { id: updatedUser.id, name: updatedUser.name };
mockUserService.updateUser.mockResolvedValue(updatedUser);
await userController.updateUser(mockReq, mockRes);
expect(mockRes.json).toHaveBeenCalledWith(updatedUser);
expect(mockUserService.updateUser).toHaveBeenCalledWith(updatedUser.id, updatedUser.id, updatedUser.name);
});
it("should return 400 if update user not found", async () => {
mockReq.params.id = "non-existent";
mockReq.body = { id: "non-existent", name: "Some Name" };
mockUserService.updateUser.mockResolvedValue(null);
await userController.updateUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({ error: "User not found" });
});
it("should delete a user", async () => {
mockReq.params.id = "1";
mockUserService.removeUser.mockResolvedValue(true);
await userController.deleteUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(204);
expect(mockUserService.removeUser).toHaveBeenCalledWith(mockReq.params.id);
});
// ============================================================ //
// 2차 업데이트 이후
// ============================================================ //
it("should return 400 if delete user not found", async () => {
mockReq.params.id = "non-existent";
mockUserService.removeUser.mockResolvedValue(false);
await userController.deleteUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({ error: "User not found" });
});
it("should return 400 if createUser is called with invalid data", async () => {
// ID 또는 이름이 누락된 경우
mockReq.body = {};
await userController.createUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ error: expect.any(String) }));
});
it("should return 400 if updateUser is called with invalid data", async () => {
// 업데이트할 사용자 정보가 유효하지 않은 경우
mockReq.params.id = "1";
mockReq.body = {};
await userController.updateUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith(expect.objectContaining({ error: "업데이트 할 내용이 없습니다." }));
});
it("should handle exceptions thrown by userService methods", async () => {
// UserService에서 예외가 발생한 경우
const errorMessage = "An error occurred";
mockUserService.createUser.mockRejectedValue(new Error(errorMessage));
mockReq.body = { id: "1", name: "Test User" };
await userController.createUser(mockReq, mockRes);
expect(mockRes.status).toHaveBeenCalledWith(400);
expect(mockRes.json).toHaveBeenCalledWith({ error: errorMessage });
});
});
대충 다 해결해서 행복하다는 표정으로 바뀐 신입입니다 ㅎ
대충 다 해결해서 행복하다는 표정으로 바뀐 신입입니다 ㅎ
A. 사실 “가능한 모든 경우의 수” 가 올바른 접근이지만, 정답은 없습니다. 회사라면 사내 rule를 따르면 됩니다!
code coverage
를 사용하구요!code coverage
가 평균 80% 를 넘지 못하면 안된다! 와 같은 기준도 생각해 볼 수 있습니다.jest.fn( () => response)
가 나오는데, 이게 체이닝..?!A. 이건 사실 문법적인 부분입니다. “Method Chaining” 을 아셔야 이해가 가능한데, 이미 여러분들이 무의식적으로 아주 많이 사용했습니다 ㅎ
res.status(201).json(user);
status
도 함수죠? 정확하게는 res
라는 object의 함수, method 입니다.res.status(201)
도 뭔가를 리턴한다는 얘기죠?this
입니다. 즉, res.status(201)
의 호출 결과 값은 res
object 라는 거에요res
object 가 제공하는 함수를 다시 호출할 수 있죠! 아래와 같이 분리할 수 있어요!const resAgain = res.status(201);
resAgain.json(user);
// resAgain === res
const response = {
status: jest.fn(() => response),
json: jest.fn(),
};
status
가 return
하는게 뭐다? 자기 자신이다~ → response
의 status
는 response
를 return
한다!spec
” 접미사가 붙나요?A. specification 의 약자고, 특정 “테스트 스팩” 을 담고 있다는 것을 의미합니다.
*.spec.js
파일은 이러한 사양이나 행동을 테스트 코드의 형태로 명세화하는 역할을 한다고 하네요!Faker - data mocking 에 도움
supertest - supertest는 HTTP assertions을 수행하기 위해 사용 / 실제 express
에서 테스트를 위해 사용한 라이브러리
factory girl