통합 테스트
라우터를 통째로 테스트해보자. routes 폴더에 auth.test.js를 작성하자. 하나의 라우터에는 여러 개의 미들웨어가 붙어 있고, 다양한 라이브러리가 사용된다. 이런 것들이 모두 유기적으로 잘 작동하는지 테스트하는 것이 통합 테스트이다.
supertest를 설치하자.
npm i -D supertest
supertest를 사용해 auth.js를 테스트할 것이다. supertest를 사용하기 위해서는 app 객체를 모듈로 만들어 분리해야 한다.
const express = require('express');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const path = require('path');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const passport = require('passport');
dotenv.config();
const pageRouter = require('./routes/page');
const authRouter = require('./routes/auth');
const postRouter = require('./routes/post');
const userRouter = require('./routes/user');
const {sequelize} = require('./models');
const passportConfig = require('./passport');
const app = express();
passportConfig(); //passport 설정
app.set('port', process.env.PORT || 8001);
app.set('view engine', 'html');
nunjucks.configure('views', {
express: app,
watch: true,
});
sequelize.sync({force: false})
.then(() => {
console.log('데이터베이스 연결 성공');
})
.catch((err) => {
console.error(err);
});
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
app.use(express.urlencoded({extended: false}));
app.use(cookieParser(process.env.COOKE_SECRET));
app.use(session({
resave: false,
saveUninitialized: false,
secret: process.env.COOKIE_SECRET,
cookie:{
httpOnly: true,
secure: false,
},
}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', pageRouter);
app.use('/auth', authRouter);
app.use('/post', postRouter);
app.use('/user', userRouter);
app.use((req, res, next) => {
const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
error.status = 404;
next(error);
});
app.use((err, req, res, next) => {
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'productiong' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
app.js
server.js로 분리하여 listen만 수행하게 한다.
const app = require('./app');
app.listen(app.get('port'), () => {
console.log(app.get('port'), '번 포트에서 대기중...');
});
server.js
script도 수정하자.
"scripts": {
"start": "nodemon server",
"test": "jest",
"coverage": "jest --coverage"
},
또한 테스트용 데이터베이스도 설정해야 한다. 통합 테스트에서는 데이터베이스 코드를 모킹하지 않으므로 데이터베이스에 실제로 테스트용 데이터가 저장된다. 그런데 실제 서비스 중인 데이터베이스에 테스트용 데이터가 들어가면 안 되므로, 테스트용 데이터베이스를 따로 만드는 것이 좋다.
config/config.json에서 test 속성을 수정하자.
"test": {
"username": "root",
"password": "비밀번호",
"database": "nodebird_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
npx sequelize db:create --env test
위의 명령어로 db를 생성한다.
이제 테스트 코드를 작성하자. routes/auth.test.js 파일을 작성한다.
const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');
beforeAll(async () => {
await sequelize.sync();
});
describe('POST /login', () => {
test('로그인 수행', async (done) => {
request(app)
.post('/auth/login')
.send({
email: '이메일@gamil.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
routes.auth.test.js
beforeAll이라는 함수는 현재 테스트를 실행하기 전에 수행되는 코드이다. 여기에 sequelize.sync()를 넣어 데이터베이스에서 테이블을 생성하고 있다.
supertest 패키지로부터 request 함수를 불러와서 app 객체를 인수로 넣는다. 여기에 get, post, put, patch, delete 등의 메서드로 원하는 라우터에 요청을 보낼 수 있다. 데이터는 send 메서드에 담아서 보낸다. 그 후에 예상되는 응답의 결과를 expect메서드의 인수로 제공하면 일치여부를 테스트한다.
테스트용 데이터베이스에는 현재 회원 정보가 없기 때문에 error가 발생한다. 로그인 라우터를 테스트하기 전에 회원가입 라우터부터 테스트해서 회원 정보를 넣어야 한다.
const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');
const { describe } = require('../models/user');
beforeAll(async () => {
await sequelize.sync();
});
describe('POST /join', () => {
test('로그인 안 했으면 가입', (done) => {
request(app)
.post('/auth/join')
.send({
email: 'eminhwan12@gmail.com',
nick: 'hvvan',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
describe('POST /join', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'eminhwan12@gmail.com',
passwod: 'nodejsbook',
})
.end(done);
});
test('이미 로그인했으면 redirect /', (done) => {
const message = encodeURIComponent('로그인한 상태입니다.');
agent
.post('/auth/join')
.send({
email: 'eminhwan12@gmail.com',
nick: 'hvvan',
password: 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
describe('POST /login', () => {
test('로그인 수행', async (done) => {
request(app)
.post('/auth/login')
.send({
email: 'eminhwan12@gamil.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
routes/auth.test.js
하지만 이미 생성된 계정을 다시 생성하면 에러가 발생한다. 테스트 후에 데이터베이스에 데이터가 남아 있으면 다음 테스트에 영향을 미칠 수도 있다. 따라서 테스트 종료 시 데이터를 정리하는 코드를 추가해야 한다.
또한, sync 메서드에 force: true를 넣어 테이블을 다시 만들게 하자.
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: 'eminhwan12@gmail.com',
nick: 'hvvan',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
describe('POST /join', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'eminhwan12@gmail.com',
passwod: 'nodejsbook',
})
.end(done);
});
test('이미 로그인했으면 redirect /', (done) => {
const message = encodeURIComponent('로그인한 상태입니다.');
agent
.post('/auth/join')
.send({
email: 'eminhwan12@gmail.com',
nick: 'hvvan',
password: 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
afterAll(async () => {
await sequelize.sync({ force: true });
})
});
describe('POST /login', () => {
test('로그인 수행', async (done) => {
request(app)
.post('/auth/login')
.send({
email: 'eminhwan12@gamil.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
이제 로그인, 로그아웃까지 테스트하며 마무리해보자.
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: 'eminhwan12@gmail.com',
nick: 'hvvan',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
describe('POST /login', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'eminhwan12@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('이미 로그인했으면 redirect /', (done) => {
const message = encodeURIComponent('로그인한 상태입니다.');
agent
.post('/auth/join')
.send({
email: 'eminhwan12@gmail.com',
nick: 'hvvan',
password: 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
describe('POST /login', () => {
test('가입되지 않은 회원', async (done) => {
const message = encodeURIComponent('가입되지 않은 회원입니다.');
request(app)
.post('/auth/login')
.send({
email: 'eminhwan@gmail.com',
password: 'nodejsbook',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
test('로그인 수행', async (done) => {
request(app)
.post('/auth/login')
.send({
email: 'eminhwan12@gmail.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
test('비밀번호 틀림', async (done) => {
const message = encodeURIComponent('비밀번호가 일치하지 않습니다.');
request(app)
.post('/auth/login')
.send({
email: 'eminhwan12@gmail.com',
password: 'wrong',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
});
describe('GET /logout', () => {
test('로그인 되어있지 않으면 403', async (done) => {
request(app)
.get('/auth/logout')
.expect(403, done);
});
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'eminhwan12@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('로그아웃 수행', async (done) => {
agent
.get('/auth/logout')
.expect('Location', `/`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true });
});
routes/auth.test.js