Node.js 테스팅
Node.js 소프트웨어 테스팅은 개발된 소프트웨어가 요구 사항에 맞게 동작하는지 검증하는 과정입니다. 이것은 미리 버그를 찾고, 품질을 높이며, 사용자 만족도를 향상시키는데 중요한 역할을 합니다.
Node.js는 서버 사이드 자바스크립트 환경으로 동시성을 지원하며 높은 성능의 네트워크 애플리케이션을 개발하기 위해 널리 사용됩니다. Node.js에서의 테스팅은 웹 서비스의 안정성과 품질을 보장하는 데 있어 중요한 요소입니다.
Node.js에서의 테스팅은 단순히 코드의 오류를 찾는 것뿐만 아니라, 개발 프로세스를 보다 견고하고 효율적으로 만들어, 개발 팀의 생산성을 향상시키고 코드의 유지보수성을 높이는 데에도 크게 기여합니다.
테스트 코드를 왜 작성할까?
코드는 완벽하지 않습니다
- 배포하고 실제 애플리케이션을 실행시킵니다.
- 에러가 발생하면 재배포해서 다시 돌려봅니다.
- 기능을 추가하거나 변경할 때마다 이전에 동작한 모든 기능들에 대해서 잘 동작하는지 돌려보면서 확인해봅니다
코드는 항상 예외적으로 에러가 발생합니다. 그럴때마다 다시 배포해서 코드를 테스트하는 것은 너무 비효율적입니다. 이러한 것을 최대한 방지하기 위하여 테스트코드를 작성합니다.
요구사항을 알 수 없습니다
- 작가 정보를 추가합니다
- 작가 테이블에는 사용자 정보가 존재해야 저장이 가능합니다
- 사용자가 탈퇴후 작가 정보를 추가한다면?
- FK 제한이 있어 에러가 발생합니다
기능을 변경하거나 확장하기 어렵습니다
- 사용자명을 20자에서 30자로 변경하려고 하는상황
- 파라미터의 글자수 제한을 변경
- 배포진행
- 하지만 DB 스키마가 반영되지 않은 상황
- 배포한 이후에 사용자명 수정 API에서 에러가 발생
유닛 테스트 vs 통합 테스트
유닛 테스트
- 개별 기능, 메서드 또는 모듈을 개별적으로 테스트
- 작고 분리된 코드 단위를 처리하므로 단위 테스트가 빠르게 실행되는 경향이 있습니다
- 모듈 내의 특정 기능이나 방법에 중점을 둡니다
- 실제로 데이터베이스에 도달하지 않고 Express.js 애플리케이션에서 특정 경로 핸들러를 테스트합니다
통합 테스트
- 시스템의 여러 부분 또는 여러 모듈간의 상호 작용을 테스트합니다
- 모방된 구성 요소나 시스템이 아닌 실제 구성 요소나 시스템을 포함합니다 (실제 데이터베이스와 상호 작용하는 Express 경로를 테스트합니다)
아래는 실제 예시를 바탕으로 진행을 해 보았습니다.
유닛 테스트
jest 패키지를 사용해서 테스트를 해보겠습니다. jest는 페이스북에서 만든 오픈 소스로, 테스팅에 필요한 툴들을 대부분 갖추고 있어 편리합니다.
jest모듈을 설치합니다.
npm i -D jest
package.json에 test라는 명령어를 등록해둡니다. test명령어를 실행하면 jest가 실행됩니다.
middlewares 폴더 안에 index.test.js를 만듭니다. 테스트용 파일은 파일명과 확장자 사이에 test나 spec을 넣으면 됩니다.
그리고 npm test로 테스트 코드를 실행할 수 있습니다.
파일명에 test나 spec이 들어간 파일들을 모두 찾아 실행합니다.
테스트를 아무것도 작성하지 않았으므로 에러가 발생합니다. 이런 경우 테스트가 실패했다고 표현합니다.
첫 번째 테스트코드를 작성해보겠습니다.
// middlewares/index.test.js
test('1 + 1 은 2 입니다.', () => {
expect(1 + 1).toEqual(2);
})
test함수의 첫 번째 인수에는 테스트에 대한 설명을 적고, 두 번째 인수인 함수에는 테스트 내용을 적습니다.
expect 함수의 인수로 실제 코드를, toEqual 함수의 인수로 예상되는 결괏값을 넣으면 됩니다.
expect에 넣은 값과 toEqual에 넣은 값이 일치하면 테스트를 통과하게 됩니다.
만약 두 값이 다르면 테스트는 실패하게 됩니다.
이제 실제 코드를 테스트 해보겠습니다.
// middlewares/index.js
exports.isLoggedIn = (req, res, next) => {
// 로그인 되어있으면 다음 라우터 이동.
if (req.isAuthenticated()) {
next();
} else {
res.status(403).send('로그인 필요');
}
};
exports.isNotLoggedIn = (req, res, next) => {
// 로그아웃 상태이면 다음 라우터 이동.
if (!req.isAuthenticated()) {
next();
}else {
const message = encodeURIComponent('로그인한 상태입니다.');
res.redirect(`/?error=${message}`);
}
};
위의 코드를 테스트 해 보겠습니다.
위의 코드는
isLoggedIn - 로그인이 되어있으면 다음라우터 이동 , 로그아웃 상태면 에러
isNotLoggedIn - 로그아웃 상태이면 다음 라우터 이동, 로그인 상태이면 에러
const {isLoggedIn , isNotLoggedIn} = require('./');
describe('isLoggedIn', () => {
test('로그인되어 있으면 isLoggedIn이 next를 호출해야 함', () => {
});
test('로그인되어 있지 않으면 isLoggedIn이 에러를 응답해야 함', () => {
});
});
describe('isNotLoggedIn', () => {
test('로그인되어 있으면 isNotLoggedIn이 에러를 응답해야 함', () => {
});
test('로그인되어 있지 않으면 isNotLoggedIn이 next를 호출해야 함', () => {
});
});
isLoggedIn 함수와 isNotLoggedIn 함수를 불러와 네 개의 테스트를 작성했습니다. 아직 내용은 입력하지 않은 상태입니다.
describe - 테스트를 그룹화 해주는 역할을 합니다.(첫 번째 인수는 그룹에 대한 설명, 두 번째 인수인 함수는 그룹에 대한 내용입니다.)
여기서 문제점이 있습니다. 실제 코드에서는 익스프레스가 req, res 객체와 next함수를 인수로 넣었기에 사용할 수 있었지만, 테스트 환경에서는 어떻게 넣어야 할 지 고민이 됩니다. req객체에는 isAuthenticated 메서드가 존재하고 res 객체에도 status, send, redirect 메서드가 존재하는데, 코드가 성공적으로 실행되게 하려면 이것들을 모두 구현해야 합니다.
가짜 객체와 함수를 만들자 - 테스트의 역할은 코드나 함수가 제대로 실행되는지를 검사하고 값이 일치하는지를 검사하는 것이므로, 테스트 코드의 객체가 실제 익스프레스 객체가 아니어도 됩니다. 이렇게 가짜 객체, 가짜 함수를 넣는것을 모킹이라고 합니다.
먼저 isLoggedIn부터 테스트를 해보겠습니다.
const {isLoggedIn , isNotLoggedIn} = require('./');
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('로그인 필요');
});
});
req, res, next를 모킹했습니다.
함수를 모킹할 때는 jest.fn 메서드를 사용합니다. 함수의 반환값을 지정하고 싶으면 jest.fn(() => 반환값)을 사용하면 됩니다.
isAuthenticated는 로그인 여부를 알려주는 함수이므로 true나 false를 반환하면 됩니다.
이때 res.status는 res.status(403).send('hello')처럼 메서드 체이닝이 가능해야 하므로 res를 반환합니다.
왜 res는 describe 함수안에서 정의 하였는데 req는 test안에서 정의하였을까요?
res는 여러 테스트에서 사용하는 모양이 같으므로 여러 테스트에서 재활용이 가능하지만, req는 isAuthenticated 메서드가 다른 모양이므로 각각의 test에 따로 선언하였습니다.
그리고 실제로는 req,res 객체에 많은 속성과 메서드가 들어 있겠지만, 지금 테스트에서는 isAuthenticated나 status, send만 사용하므로 나머지는 제외해도 무방합니다.
이 코드를 test 해보겠습니다.
테스트를 돌려보면 모두 통과합니다.
작성하지 않은 두 개의 테스트(isNotLoggedIn)도 통과합니다. 바로 이것이 테스트한다고 해서 에러가 발생하지 않는다고 단정할 수 없는 이유입니다.
테스트 대상을 잘못 선정하거나 잘못된 방식으로 테스트한 경우에는 테스트를 작성했더라도 에러가 발생할 수 있습니다.
위와 같은 이유 때문에 테스트를 올바르게 작성하는 데는 많은 훈련과 연습이 필요합니다.
isNotLoggedIn 부분도 마저 작성해 보겠습니다.
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).toHaveBeenCalledTimes(1);
});
});
로그아웃 상태이면 => next로 다음 라우터 호출
로그인 상태이면 => 에러를 호출
isLoggedIn과 isNotLoggedIn코드를 test하는 코드입니다.
const {isLoggedIn , isNotLoggedIn} = require('./');
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).toHaveBeenCalledTimes(1);
});
});
테스트 결과입니다.
4개의 테스트 모두 통과하였습니다.
이렇게 작은 단위의 함수나 모듈이 의도된대로 정확히 작동하는지 테스트하는 것을 유닛 테스트 또는 단위 테스트라고 합니다.
주의할점은 나중에 함수를 수정하면 기존에 작성해둔 테스트는 실패하게 됩니다. 따라서 함수가 수정되었을 때는 어떤 부분이 고장 나는지를 테스트를 통해 알 수 있습니다. 테스트 코드도 변경된 것에 맞춰 수정해야 합니다.
다음 파일도 테스트해보겠습니다.
user 컨트롤러도 테스트해보겠습니다.
controllers/user.js 파일입니다.
const User = require('../models/user');
exports.follow = async (req, res, next) => {
try{
const user = await User.findOne({ where : { id: req.user.id }});
if (user) { // req.user.id가 followerId, req.params.id가 followingId
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);
}
};
위의 코드는 유저 정보를 req로 받으면 해당 user.id에서 찾아오는 것입니다.
해당 id의 user가 존재하면 Following에 추가하고 'success'를 send해줍니다.
위의 코드의 test코드를 작성을 했습니다.
const { follow } = require('./user');
describe('follow', () => {
// req, res, next 모킹
const req = {
user : { id : 1},
params : { id : 2},
};
const res = {
status : jest.fn(() => res),
send : jest.fn(),
};
const next = jest.fn();
test('사용자를 찾아 팔로잉을 추가하고 success를 응답해야 함', async () => {
await follow(req, res, next);
expect(res.send).toBeCalledWith('success');
});
test('사용자를 못찾으면 res.status(404).send(no user)를 호출함', async () => {
await follow(req, res, next);
expect(res.status).toBeCalledWith(404);
expect(res.send).toBeCalledWith('no user');
})
test('DB에서 에러가 발생하면 next(error)를 호출함', async () => {
const message = 'DB에러';
await follow(req, res, next);
expect(next).toBeCalledWith(message);
})
})
위의 코드를 실패합니다.
그렇다면 왜 실패할까요????
바로 User모델 때문입니다. follow 컨트롤러 안에는 User 라는 모델이 들어있습니다. 이 모델은 실제 데이터베이스와 연결되어 있으므로 테스트 환경에서는 사용할 수 없습니다. 따라서 User모델도 모킹을 해야합니다.
jest에서는 jest.mock메서드를 사용하며 모킹을 할 수 있습니다.
jest.mock('../models/user');
const User = require('../models/user');
const { follow } = require('./user');
describe('follow', () => {
// req, res, next 모킹
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({
addFollowing(id) {
return Promise.resolve(true);
}
});
await follow(req, res, next);
expect(res.send).toBeCalledWith('success');
});
test('사용자를 못찾으면 res.status(404).send(no user)를 호출함', async () => {
User.findOne.mockReturnValue(null);
await follow(req, res, next);
expect(res.status).toBeCalledWith(404);
expect(res.send).toBeCalledWith('no user');
})
test('DB에서 에러가 발생하면 next(error)를 호출함', async () => {
const message = 'DB에러';
User.findOne.mockReturnValue(Promise.reject(message));
await follow(req, res, next);
expect(next).toBeCalledWith(message);
})
})
jest.mock 메서드에 모킹할 모듈의 경로를 인수로 넣고 그 모듈을 불러옵니다. 이러면 해당 모듈의 메서드는 전부 가짜 메서드가 됩니다.
예를들어 User.findOne 등의 가짜 메서드가 됩니다.
위의 상황을 하나하나 봐보도록 하겠습니다.
User.findOne.mockReturnValue({
addFollowing(id) {
return Promise.resolve(true);
}
});
첫번째 테스트에서 mockReturnValue 메서드를 통해 User.findOne 이 { addFollowing() } 객체를 반환하도록 하였습니다.
이는 DB로부터 사용자를 찾은 후 팔로잉을 추가하는 상황을 테스트하기 위함입니다.
두번째 테스트
test('사용자를 못찾으면 res.status(404).send(no user)를 호출함', async () => {
User.findOne.mockReturnValue(null);
await follow(req, res, next);
expect(res.status).toBeCalledWith(404);
expect(res.send).toBeCalledWith('no user');
})
두번째 테스트에서 User.findOne이 null을 반환해 사용자를 찾지 못한 상황을 테스트하였습니다.
세번째 테스트
test('DB에서 에러가 발생하면 next(error)를 호출함', async () => {
const message = 'DB에러';
User.findOne.mockReturnValue(Promise.reject(message));
await follow(req, res, next);
expect(next).toBeCalledWith(message);
})
세 번째 테스트에서는 Promise.reject로 에러가 발생하도록 했습니다.
DB 연결에 에러가 발생한 상황을 모킹한 것입니다. 실제코드에서는 catch문으로 이동합니다.
이제 다시 test를 해보면 통과합니다.
위에서는 실제 데이터베이스에 팔로잉을 등록하는 것이 아니므로 제대로 테스트되는 것인지 걱정이 되었습니다.
이처럼 테스트를 해도 실제 서비스의 실제 데이터베이스에서는 문제가 발생할 수 있습니다. 그럴 때는 유닛테스트 말고 다른 종류의 테스트를 진행해야 합니다. 이를 점검하기위해 통합 테스트나 시스템 테스트를 합니다.
테스트 커버리지
전체 코드중에서 어떤 부분이 테스트되고 어떤 부분이 테스트되지 않는지를 알아야합니다.
어떤 부분이 테스트되지 않는지를 알아야 나중에 그 부분의 테스트 코드를 작성할 수 있습니다.
전체 코드 중에서 테스트되고 있는 코드의 비율과 테스트되고 있지 않은 코드의 위치를 알려주는 jest의 기능이 있습니다.
바로 커버리지 기능 입니다.
커버리지 기능을 사용해보겠습니다.
먼저 커버리지 기능을 사용하기 위해 package.json에 jest 설정을 입력합니다.
jest 명령어 뒤에 --coverage 옵션을 붙이면 jest가 테스트 커버리지를 분석합니다.
그럼 커버리지를 실행 해 보겠습니다.
테스트 결과가 출력되고 추가적으로 표가 하나 더 출력됩니다.
표의열은 각각 File(파일과 폴더 이름), % Stmt(구문 비율), % Branch(if문 등의 분기점 비율), % Funcs(함수 비율), % Lines(코드 줄 수 비율), Uncovered Line #s(커버되지 않은 줄 위치)입니다. 퍼센티지가 높을수록 더 많은 코드가 테스트 되었다는 의미입니다.
표를 분석해보면 84,100,60,84퍼센트의 비율로 테스트 되었음을 알 수 있습니다.
여기에서는 명시적으로 테스트하고 require한 코드만 커버리지 분석이 됩니다.
그러므로, All files라 하더라도 현재 controllers/user.js, models/user.js, middlewares/index.js만 포함되어 있습니다.
따라서, 테스트 커버리지가 100%라 하더라도 실제로 모든 코드를 테스트한 것은 아닙니다.
커버리지를 보아하니 models/user 부분이 require되어서 모킹되었는데 이 부분이 테스트 되지 않아 비율이 이렇게 나옵니다.
그럼 models/user도 test코드를 작성 해 보겠습니다.
const Sequelize = require('sequelize');
const fs = require('fs');
const path = require('path');
const env = process.env.NODE_ENV || 'development';
const config = require('../config/config.json')[env];
const db = {}
const sequelize = new Sequelize(
config.database, config.username, config.password, config,
);
db.sequelize = sequelize;
const basename = path.basename(__filename);
fs
.readdirSync(__dirname) // 현재 폴더의 모든 파일을 조회
.filter( file =>{ // 숨김 파일, index.js, js 확장자가 아닌 파일 필터링
return((file.indexOf('.') !== 0) && !file.includes('test') &&(file !== basename) && (file.slice(-3) === '.js'));
})
.forEach( file => { // 해당 파일의 모델을 불러와서 init
const model = require(path.join(__dirname, file));
console.log(file, model.name);
db[model.name] = model;
model.initiate(sequelize);
});
Object.keys(db).forEach(modelName => { // associate 호출
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
module.exports = db;
static initiate , static associate 메서드를 test하는 코드를 작성하였습니다.
아래는 커버리지를 실행했을 때 결과입니다.
통합 테스트
하나의 라우터에는 여러 개의 미들웨어가 붙어 있고 다양한 라이브러리가 사용됩니다. 이런 것들이 모두 유기적으로 잘 작동하는지 테스트하는 것이 통합 테스트입니다.
주의할 점이 있는는데 통합 테스트에느 데이터베이스를 모킹하지 않으므로 데이터베이스에 실제로 테스트용 데이터가 저장됩니다. 실제 서비스중인 데이터베이스에 테스트용 데이터가 들어가면 안되므로, 테스트용 데이터베이스를 따로 만드는 것이 좋습니다.
const express = require('express');
const passport = require('passport');
const { isLoggedIn, isNotLoggedIn } = require('../middlewares');
const { join, login, logout} = require('../controllers/auth');
const router = express.Router();
// POST /auth/join
router.post('/join', isNotLoggedIn, join);
// POST /auth/login
router.post('/login', isNotLoggedIn, login);
// GET /auth/logout
router.get('/logout', isLoggedIn, logout);
// GET /auth/kakao
router.get('/kakao', passport.authenticate('kakao'));
// GET /auth/kakao/callback
router.get('/kakao/callback', passport.authenticate('kakao', {
failureRedirect: '/?error=카카오로그인 실패',
}), (req, res) => {
res.redirect('/'); // 성공 시에는 /로 이동
});
module.exports = router;
이 로그인 라우터들의 테스트코드를 작성 해 보겠습니다.
const bcrypt = require('bcrypt');
const passport = require('passport');
const User = require('../models/user');
exports.join = async (req, res, next) => {
const { email, nick, password} = req.body;
try{
const exUser = await User.findOne({ where : { email }});
if (exUser) {
return res.redirect('/join?error=exist');
}
const hash = await bcrypt.hash(password, 12);
await User.create({
email,
nick,
password: hash,
});
return res.redirect('/');
}catch(error){
console.error(error);
return next(error);
}
}
exports.login = (req, res, next) => {
passport.authenticate('local', (authError, user, info) => {
if (authError) {
console.error(authError);
return next(authError);
}
if (!user) {
return res.redirect(`/?error=${info.message}`);
}
return req.login( user, (loginError) => {
if (loginError) {
console.error(loginError);
return next(loginError);
}
return res.redirect('/');
});
})(req, res, next); // 미들웨어 내의 미들웨어는 (req, res, next)를 붙입니다.
};
exports.logout = (req, res) => {
req.logout(() => {
res.redirect('/');
});
};
회원가입, 로그인, 로그아웃의 컨트롤러 입니다.
const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');
beforeAll(async () => {
await sequelize.sync();
});
describe('POST /login', () => {
test('로그인 수행', (done) => {
request(app)
.post('/auth/login')
.send({
email : 'abc1234@naver.com',
password : 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done)
})
})
로그인 테스트코드 입니다.
실행결과 실패하였습니다.
위의 코드를 살펴 보겠습니다.
- beforAll : 모든 테스트를 실행하기 전에 수행해야 할 코드를 넣는 공간입니다. 여기에 sequelize.sync()를 넣어 데이터베이스에 테이블을 생성하고 있습니다. 비슷한 함수로 afterAll(모든 테스트가 끝난 후), beforeEach(각각의 테스트 수행 전), afterEach(각각의 테스트 수행 후)가 있습니다.
- supertest 패키지로부터 request 함수를 불러와서 app객체를 인수로 넣습니다. 여기에 get, post, put, patch, delete 등의 메서드로 원하는 라우터에 요청을 보낼 수 있습니다. 데이터는 send에 담아서 보냅니다.
테스트가 실패하는 이유는 현재 회원 정보가 없습니다. 따라서 로그인 라우터를 테스트 하기 전에 회원 가입 라우터부터 테스트해서 회원 정보를 넣어야 합니다.
const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');
beforeAll(async () => {
await sequelize.sync();
});
describe('POST /join', () => {
test('로그인 안 했으면 가입', (done) => {
request(app)
.post('/auth/join')
.send({
email: 'abc1234@naver.com',
nick : 'leekc',
password : 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done)
});
});
describe('POST /join', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email : 'abc1234@naver.com',
password : 'nodejsbook',
})
.end(done);
});
test(' 이미 로그인했으면 redirect /', (done) => {
const message = encodeURIComponent('로그인한 상태입니다.');
agent
.post('/auth/join')
.send({
email : 'abc1234@naver.com',
nick : 'leekc',
password : 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true})
});
따라서 join의 test코드부터 작성 해 주었습니다.
첫 번째 describe에는 회원 가입을 테스트합니다.
두 번째 describe에는 로그인한 상태에서 회원 가입을 시도하는 경우를 테스트합니다.
이때 로그인한 상태여야 회원 가입을 테스트할 수 있으므로 로그인 요청과 회원 가입 요청이 순서대로 이뤄져야 합니다.
테스트 후 데이터베이스에 데이터가 남아 있으면 다음 테스트에 영향을 미칠 수 있으므로, 테스트 종료 시 데이터를 정리하는 코드를 추가해줍니다.
const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');
beforeAll(async () => {
await sequelize.sync();
});
// 회원가입 테스트
describe('POST /join', () => {
test('로그인 안 했으면 가입', (done) => {
request(app)
.post('/auth/join')
.send({
email: 'abc1234@naver.com',
nick : 'leekc',
password : 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done)
});
});
describe('POST /join', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email : 'abc1234@naver.com',
password : 'nodejsbook',
})
.end(done);
});
test(' 이미 로그인했으면 redirect /', (done) => {
const message = encodeURIComponent('로그인한 상태입니다.');
agent
.post('/auth/join')
.send({
email : 'abc1234@naver.com',
nick : 'leekc',
password : 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
// 로그인 테스트
describe('POST /login', () => {
test('가입되지 않은 회원', (done) => {
const message = encodeURIComponent('가입되지 않은 회원입니다.');
request(app)
.post('/auth/login')
.send({
email : 'abc12@naver.com',
password : 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
test('로그인 수행', (done) => {
request(app)
.post('/auth/login')
.send({
email : 'abc1234@naver.com',
password : 'nodejsbook',
})
.expect('Location', '/')
.expect(302,done);
})
test('비밀번호 틀림', (done) => {
const message = encodeURIComponent('비밀번호가 일치하지 않습니다.');
request(app)
.post('/auth/login')
.send({
email : 'abc1234@naver.com',
password : 'wrong',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
})
// 로그아웃 테스트
describe('GET /logout', () => {
test('로그인되어 있지 않으면 403', (done) => {
request(app)
.get('/auth/logout')
.expect(403, done);
});
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email : 'abc1234@naver.com',
password : 'nodejsbook',
})
.end(done);
});
test('로그아웃 수행', (done) => {
agent
.get('/auth/logout')
.expect('Location', '/')
.expect(302, done)
});
});
afterAll(async () => {
await sequelize.sync({ force: true})
});
회원가입, 로그인, 로그아웃에 대한 테스트코드를 작성을 했습니다.
'Node.js' 카테고리의 다른 글
로드밸런싱, 로드밸런서, 네트워크 계층 (0) | 2024.01.12 |
---|---|
Redis Deep Dive (0) | 2024.01.11 |
[DataBase] DataBase 필요한이유, 등장배경, 내부동작 (0) | 2024.01.09 |
[Node.js] Libuv 이벤트 루프 (2) | 2024.01.08 |
[Node.js] node.js 웹 라이브러리 (1) | 2024.01.07 |