유닛 테스트(2)
isNotLoggedIn 부분도 마저 작성하겠습니다.
const { isLoggedIn, isNotLoggedIn } = require("./middlewares");
describe("isLoggedIn", () => {
const res = {
status: jest.fn(() => res),
send: jest.fn(),
};
const next = jest.fn();
test("로그인 되어있으면 isLoggedIn이 next를 호출해야 함", () => {
const req = {
isAuthenticated: jest.fn(() => true),
};
isLoggedIn(req, res, next);
expect(next).toBeCalledTimes(1);
});
test("로그인 되어있지 않으면 isLoggedIn이 에러를 응답해야 함", () => {
const req = {
isAuthenticated: jest.fn(() => false),
};
isLoggedIn(req, res, next);
expect(res.status).toBeCalledWith(403);
expect(res.send).toBeCalledWith("로그인 필요");
});
});
describe("isNotLoggedIn", () => {
const res = {
redirect: jest.fn(),
};
const next = jest.fn();
test("로그인되어 있으면 isNotLoggedIn이 에러를 응답해야 함", () => {
const req = {
isAuthenticated: jest.fn(() => true),
};
isNotLoggedIn(req, res, next);
const message = encodeURIComponent("로그인한 상태입니다.");
expect(res.redirect).toBeCalledWith(`/error=${message}`);
});
test("로그인되어 있지 않으면 isNotLoggedIn이 next를 호출해야 함", () => {
const req = {
isAuthenticated: jest.fn(() => false),
};
isNotLoggedIn(req, res, next);
expect(next).toBeCalledTimes(1);
});
});
테스트는 통과할 것입니다. 이렇게 작은 단위의 함수나 모듈이 의도된 대로 정확히 작동하는지 테스트하는 것을 유닛 테스트(unit test) 또는 단위 테스트라고 부릅니다. 나중에 함수를 수정하면 기존에 작성해둔 테스트는 실패하게 됩니다. 따라서 함수가 수정되었을 때 어떤 부분이 고장났는지를 테스트를 통해 알 수 있습니다. 테스트 코드도 기준 코드가 변경된 것에 맞춰서 수정해야 합니다.
라우터와 긴밀하게 연관되어 있는 미들웨어도 테스트해보겠습니다. 단, 이때는 유닛 테스트를 위해 미들웨어를 분리해야 합니다. routes/user.js 파일을 다시 한 번 보겠습니다.
const express = require("express");
const { isLoggedIn } = require("./middlewares");
const User = require("../models/user");
const router = express.Router();
router.post("/:id/follow", isLoggedIn, async (req, res, next) => {
try {
const user = await User.findOne({ where: { id: req.user.id } });
if (user) {
await user.addFollowing(parseInt(req.params.id, 10));
res.send("success");
} else {
res.status(400).send("no user");
}
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;
POST /:id/follow 라우터의 async 함수 부분은 따로 분리할 수 있습니다. controllers 폴더를 만들고 그 안에 user.js를 만듭니다. 라우터에서 응답을 보내는 미들웨어를 특별히 컨트롤러라고 부릅니다.
const User = require("../models/user");
exports.addFollowing = async (req, res, next) => {
try {
const user = await User.findOne({ where: { id: req.user.id } });
if (user) {
await user.addFollowing(parseInt(req.params.id, 10));
res.send("success");
} else {
res.status(404).send("no user");
}
} catch (error) {
console.error(error);
next(error);
}
};
컨트롤러를 분리했으므로 routes/user.js도 따라서 수정합니다.
const express = require("express");
const { isLoggedIn } = require("./middlewares");
const { addFollowing } = require("../controllers/user");
const router = express.Router();
router.post("/:id/follow", isLoggedIn, addFollowing);
module.exports = router;
이제 addFollowing 컨트롤러를 테스트해야 하는데요. controllers/user.test.js를 작성합니다.
const { addFollowing } = require("./user");
describe("addFollowing", () => {
const req = {
user: { id: 1 },
params: { id: 2 },
};
const res = {
status: jest.fn(() => res),
send: jest.fn(),
};
const next = jest.fn();
test("사용자를 찾아 팔로잉을 추가하고 success를 응답해야 함", async () => {
User.findOne.mockReturnValue(
Promise.resolve({
addFollowing(id) {
return Promise.resolve(true);
},
})
);
await addFollowing(req, res, next);
expect(res.send).toBeCalledWith("success");
});
test("사용자를 못 찾으면 next(error)를 호출함", async () => {
const error = "사용자 못 찾음";
User.findOne.mockReturnValue(Promise.reject(error));
await addFollowing(req, res, next);
expect(next).toBeCalledWith(error);
});
});
addFollowing 컨트롤러가 async 함수이므로 await을 붙여야 컨트롤러가 실행 완료 후 expect 함수가 실행됩니다. 그런데 이 테스트는 실패합니다.
$ npm test
> nodebird@0.0.1 test
> jest
(node:21832) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:8596) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
FAIL routes/middlewares.test.js (11.646s)
● isNotLoggedIn › 로그인되어 있으면 isNotLoggedIn이 에러를 응답해야 함
expect(jest.fn()).toBeCalledWith(...expected)
Expected: "/error=%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C%20%EC%83%81%ED%83%9C%EC%9E%85%EB%8B%88%EB%8B%A4."
Received: "/?error=%EB%A1%9C%EA%B7%B8%EC%9D%B8%ED%95%9C%20%EC%83%81%ED%83%9C%EC%9E%85%EB%8B%88%EB%8B%A4."
Number of calls: 1
37 | isNotLoggedIn(req, res, next);
38 | const message = encodeURIComponent("로그인한 상태입니다.");
> 39 | expect(res.redirect).toBeCalledWith(`/error=${message}`);
| ^
40 | });
41 |
42 | test("로그인되어 있지 않으면 isNotLoggedIn이 next를 호출해야 함", () => {
at Object.<anonymous> (routes/middlewares.test.js:39:26)
FAIL controllers/user.test.js (18.077s)
● addFollowing › 사용자를 찾아 팔로잉을 추가하고 success를 응답해야 함
ReferenceError: User is not defined
13 |
14 | test("사용자를 찾아 팔로잉을 추가하고 success를 응답해야 함", async () => {
> 15 | User.findOne.mockReturnValue(
| ^
16 | Promise.resolve({
17 | addFollowing(id) {
18 | return Promise.resolve(true);
at Object.<anonymous> (controllers/user.test.js:15:5)
● addFollowing › 사용자를 못 찾으면 next(error)를 호출함
ReferenceError: User is not defined
26 | test("사용자를 못 찾으면 next(error)를 호출함", async () => {
27 | const error = "사용자 못 찾음";
> 28 | User.findOne.mockReturnValue(Promise.reject(error));
| ^
29 | await addFollowing(req, res, next);
30 | expect(next).toBeCalledWith(error);
31 | });
at Object.<anonymous> (controllers/user.test.js:28:5)
Test Suites: 2 failed, 2 total
Tests: 3 failed, 3 passed, 6 total
Snapshots: 0 total
Time: 37.987s
Ran all test suites.
바로 User 모델 때문입니다. addFollowing 컨트롤러 안에는 User라는 모델이 들어 있습니다. 이 모델은 실제 데이터베이스와 연결되어 있으므로 테스트 환경에서는 사용할 수 없습니다.따라서 User 모델로 보정해야 합니다.
다음 NODE JS 글에서 계속....