์์ ์ด ๋ง๋ ์๋น์ค๊ฐ ์ ๋๋ก ๋์ํ๋์ง ํ ์คํธํด์ผ ํจ
npm i โD jest
...
{
"name": "nodebird",
"version": "0.0.1",
"description": "์ต์คํ๋ ์ค๋ก ๋ง๋๋ SNS ์๋น์ค",
"main": "app.js",
"scripts": {
"start": "nodemon app",
"test": "jest"
},
...
routes ํด๋ ์์ middlewares.test.js ์์ฑ
middlewares.test.js ์์ฑํ๊ธฐ
๐ปroutes/middlewares.test.js
test('1 + 1์ 2์
๋๋ค.', () => {
expect(1 + 1).toEqual(2);
});
๋ ์ธ์๋ฅผ ๋ค๋ฅด๊ฒ ์์ฑํ๋ฉด ์คํจ(๋ฉ์์ง๋ฅผ ์ดํด๋ณผ ๊ฒ)
๐ปroutes/middlewares.test.js
test('1 + 1์ 2์
๋๋ค.', () => {
expect(1 + 1).toEqual(2);
});
middlewares.test.js ์์ฑํ๊ธฐ
๐ปroutes/middlewares.test.js
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
//๊ทธ๋ฃนํ -> describe
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);
});
});
๋ฏธ๋ค์จ์ด ํ
์คํธ๋ฅผ ์ํด req์ res๋ฅผ ๊ฐ์ง๋ก ๋ง๋ค์ด์ฃผ์ด์ผ ํจ
jest.fn์ผ๋ก ํจ์ ๋ชจํน ๊ฐ๋ฅ
๐ปroutes/middlewares.test.js
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).toHaveBeenCalledTimes(1);
});
});
expect์๋ toEqual ๋ง๊ณ ๋ ๋ง์ ๋ฉ์๋ ์ง์
๐ปroutes.middlewares.test.js
...
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);
});
});
๋ผ์ฐํฐ๋ ๋ฏธ๋ค์จ์ด์ด๋ฏ๋ก ๋ถ๋ฆฌํด์ ํ ์คํธ ๊ฐ๋ฅ
๐ป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;
Controllers/user.test.js ์์ฑํ๊ธฐ
๐ปControllers/user.test.js
jest.mock('../models/user');
const User = require('../models/user');
const { addFollowing } = require('../controllers/user');
describe('addFollowing', () => {
const req = {
user: { id: 1 },
params: { id: 2 },
};
const 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);
});
});
Jest๋ฅผ ์ฌ์ฉํด ๋ชจ๋ ๋ชจํน ๊ฐ๋ฅ(jest.mock)
๐ปcontroller/user.test.js
jest.mock('../models/user');
const User = require('../models/user');
const { addFollowing } = require('../controllers/user');
describe('addFollowing', () => {
const req = {
user: { id: 1 },
params: { id: 2 },
};
const 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);
});
});
์ ์ฒด ์ฝ๋ ์ค์์ ํ ์คํธ๋๊ณ ์๋ ์ฝ๋์ ๋น์จ
๐ปpackage.json
{
"name": "nodebird",
"version": "0.0.1",
"description": "์ต์คํ๋ ์ค๋ก ๋ง๋๋ SNS ์๋น์ค",
"main": "app.js",
"scripts": {
"start": "nodemon server",
"test": "jest"
},
"author": "seokahi",
"license": "MIT",
"dependencies": {
"bcrypt": "^3.0.7",
"cookie-parser": "^1.4.3",
"dotenv": "^8.2.0",
"express": "^4.16.3",
"express-session": "^1.15.6",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"mysql2": "^2.0.2",
"nunjucks": "^3.2.0",
"passport": "^0.4.0",
"passport-kakao": "1.0.0",
"passport-local": "^1.0.0",
"sequelize": "^5.21.3",
"sequelize-cli": "^5.5.1"
},
"devDependencies": {
"jest": "^24.9.0",
"nodemon": "^2.0.2",
"supertest": "^4.0.2"
}
}
models/users.js์ 5, 41, 42, 47์ค ํ์ธ
๐ปmodels/user.js
const Sequelize = require('sequelize');
module.exports = class User extends Sequelize.Model {
static init(sequelize) {
return super.init({
email: {
type: Sequelize.STRING(40),
allowNull: true,
unique: true,
},
nick: {
type: Sequelize.STRING(15),
allowNull: false,
},
password: {
type: Sequelize.STRING(100),
allowNull: true,
},
provider: {
type: Sequelize.STRING(10),
allowNull: false,
defaultValue: 'local',
},
snsId: {
type: Sequelize.STRING(30),
allowNull: true,
},
}, {
sequelize,
timestamps: true,
underscored: false,
modelName: 'User',
tableName: 'users',
paranoid: true,
charset: 'utf8',
collate: 'utf8_general_ci',
});
}
static associate(db) {
db.User.hasMany(db.Post);
db.User.belongsToMany(db.User, {
foreignKey: 'followingId',
as: 'Followers',
through: 'Follow',
});
db.User.belongsToMany(db.User, {
foreignKey: 'followerId',
as: 'Followings',
through: 'Follow',
});
}
};
models/users.test.js์์ฑ
๐ปmodels/users.test.js
const Sequelize = require('sequelize');
const User = require('./user');
const config = require('../config/config')['test'];
const sequelize = new Sequelize(
config.database, config.username, config.password, config,
);
describe('User ๋ชจ๋ธ', () => {
test('static init ๋ฉ์๋ ํธ์ถ', () => {
expect(User.init(sequelize)).toBe(User);
});
test('static associate ๋ฉ์๋ ํธ์ถ', () => {
const db = {
User: {
hasMany: jest.fn(),
belongsToMany: jest.fn(),
},
Post: {},
};
User.associate(db);
expect(db.User.hasMany).toHaveBeenCalledWith(db.Post);
expect(db.User.belongsToMany).toHaveBeenCalledTimes(2);
});
});
๋ชจ๋ ์ฝ๋๊ฐ ํ ์คํธ๋์ง ์๋๋ฐ๋ ์ปค๋ฒ๋ฆฌ์ง๊ฐ 100%์
๋ผ์ฐํฐ ํ๋๋ฅผ ํต์งธ๋ก ํ ์คํธํด ๋ด(์ฌ๋ฌ ๊ฐ์ ๋ฏธ๋ค์จ์ด, ๋ชจ๋์ ํ ๋ฒ์ ํ ์คํธ).
๐ปapp.js
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(); // ํจ์คํฌํธ ์ค์
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('/img', express.static(path.join(__dirname, 'uploads')));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_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) => {
console.error(err);
res.locals.message = err.message;
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
server.js
const app = require('./app');
app.listen(app.get('port'), () => {
console.log(app.get('port'), '๋ฒ ํฌํธ์์ ๋๊ธฐ์ค');
});
๐ปpackage.json
{
"name": "nodebird",
"version": "0.0.1",
"description": "์ต์คํ๋ ์ค๋ก ๋ง๋๋ SNS ์๋น์ค",
"main": "server.js",
"scripts": {
"start": "nodemon server",
"test": "jest"
},
"author": "seokahi",
"license": "MIT",
"dependencies": {
"bcrypt": "^3.0.7",
"cookie-parser": "^1.4.3",
"dotenv": "^8.2.0",
"express": "^4.16.3",
"express-session": "^1.15.6",
"morgan": "^1.9.1",
"multer": "^1.4.2",
"mysql2": "^2.0.2",
"nunjucks": "^3.2.0",
"passport": "^0.4.0",
"passport-kakao": "1.0.0",
"passport-local": "^1.0.0",
"sequelize": "^5.21.3",
"sequelize-cli": "^5.5.1"
},
"devDependencies": {
"jest": "^24.9.0",
"nodemon": "^2.0.2",
"supertest": "^4.0.2"
}
}
๊ฐ๋ฐ/๋ฐฐํฌ์ฉ DB๋ ๋ณ๋๋ก ์ค์ ํ๋ ๊ฒ์ด ์ข์
๐ปconfig/config.json
{
"development": {
"username": "root",
"password": "nodejsbook",
"database": "nodebird",
"host": "127.0.0.1",
"dialect": "mysql"
},
"test": {
"username": "root",
"password": "nodejsbook",
"database": "nodebird_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}
routes/auth.test.js ์์ฑ
๐ปroutes/auth.test.js
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: 'zerohch0@gmail.com',
nick: 'zerocho',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
describe('POST /login', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('์ด๋ฏธ ๋ก๊ทธ์ธํ์ผ๋ฉด redirect /', (done) => {
const message = encodeURIComponent('๋ก๊ทธ์ธํ ์ํ์
๋๋ค.');
agent
.post('/auth/join')
.send({
email: 'zerohch0@gmail.com',
nick: 'zerocho',
password: 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
describe('POST /login', () => {
test('๊ฐ์
๋์ง ์์ ํ์', (done) => {
const message = encodeURIComponent('๊ฐ์
๋์ง ์์ ํ์์
๋๋ค.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch1@gmail.com',
password: 'nodejsbook',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
test('๋ก๊ทธ์ธ ์ํ', (done) => {
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
test('๋น๋ฐ๋ฒํธ ํ๋ฆผ', (done) => {
const message = encodeURIComponent('๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'wrong',
})
.expect('Location', `/?loginError=${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: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('๋ก๊ทธ์์ ์ํ', (done) => {
agent
.get('/auth/logout')
.expect('Location', `/`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true });
});
routes/auth.test.js ์์
๐ปroutes/auth.test.js
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: 'zerohch0@gmail.com',
nick: 'zerocho',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
describe('POST /login', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('์ด๋ฏธ ๋ก๊ทธ์ธํ์ผ๋ฉด redirect /', (done) => {
const message = encodeURIComponent('๋ก๊ทธ์ธํ ์ํ์
๋๋ค.');
agent
.post('/auth/join')
.send({
email: 'zerohch0@gmail.com',
nick: 'zerocho',
password: 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
describe('POST /login', () => {
test('๊ฐ์
๋์ง ์์ ํ์', (done) => {
const message = encodeURIComponent('๊ฐ์
๋์ง ์์ ํ์์
๋๋ค.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch1@gmail.com',
password: 'nodejsbook',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
test('๋ก๊ทธ์ธ ์ํ', (done) => {
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
test('๋น๋ฐ๋ฒํธ ํ๋ฆผ', (done) => {
const message = encodeURIComponent('๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'wrong',
})
.expect('Location', `/?loginError=${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: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('๋ก๊ทธ์์ ์ํ', (done) => {
agent
.get('/auth/logout')
.expect('Location', `/`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true });
});
routes/auth.test.js ์์
๐ปauth.test.js
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: 'zerohch0@gmail.com',
nick: 'zerocho',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
});
describe('POST /login', () => {
const agent = request.agent(app);
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('์ด๋ฏธ ๋ก๊ทธ์ธํ์ผ๋ฉด redirect /', (done) => {
const message = encodeURIComponent('๋ก๊ทธ์ธํ ์ํ์
๋๋ค.');
agent
.post('/auth/join')
.send({
email: 'zerohch0@gmail.com',
nick: 'zerocho',
password: 'nodejsbook',
})
.expect('Location', `/?error=${message}`)
.expect(302, done);
});
});
describe('POST /login', () => {
test('๊ฐ์
๋์ง ์์ ํ์', (done) => {
const message = encodeURIComponent('๊ฐ์
๋์ง ์์ ํ์์
๋๋ค.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch1@gmail.com',
password: 'nodejsbook',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
test('๋ก๊ทธ์ธ ์ํ', (done) => {
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
test('๋น๋ฐ๋ฒํธ ํ๋ฆผ', (done) => {
const message = encodeURIComponent('๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'wrong',
})
.expect('Location', `/?loginError=${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: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('๋ก๊ทธ์์ ์ํ', (done) => {
agent
.get('/auth/logout')
.expect('Location', `/`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true });
});
routes/auth.test.js ์์
...
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: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
test('๋ก๊ทธ์์ ์ํ', (done) => {
agent
.get('/auth/logout')
.expect('Location', `/`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true });
});
์๋ฒ๊ฐ ์ผ๋ง๋งํผ์ ์์ฒญ์ ๊ฒฌ๋ ์ ์๋์ง ํ ์คํธ
์ ์ฝ์์์ ๋ค์ ๋ช ๋ น์ด ์ ๋ ฅ
loadtest.json์ ์ฌ์ฉ์์ ํ๋ ํ๋ฆ ์์ฑ ๊ฐ๋ฅ
๐ปloadtest.json
{
"config":{
"target": "http://localhost:8001",
"phases": [
{
"duration": 60,
"arrivalRate": 30
}
]
},
"scenarios": [{
"flow": [{
"get": {
"url": "/"
}
}, {
"post": {
"url": "/auth/login",
"json": {
"email": "zerohch0@naver.com",
"password": "nodejsbook"
}
}
}, {
"get": {
"url": "/hashtag?hashtag=nodebird"
}
}]
}]
}
๋ฌธ์ ์ ๋ฐ๊ฒฌ
์์ฒญ ํ๋ฐ๋ถ๊ฐ ๋ ์๋ก ์๋ต ์๊ฐ์ด ๊ธธ์ด์ง
์ฒซ ์๋ต์ 4.7๋ฐ๋ฆฌ์ด, ๋ง์ง๋ง ์๋ต์ 51์ด
5400๊ฐ์ ์์ฒญ์ 200 ์๋ต์ฝ๋, 1800๊ฐ๋ 302
์๋ฒ๊ฐ ์ง๊ธ ์ ๋์ ์์ฒญ์ ๊ฐ๋นํ์ง ๋ชปํจ
์๋ฒ ์ฌ์์ ์ ๊ทธ๋ ์ด๋ํ๊ฑฐ๋, ์ฌ๋ฌ ๊ฐ ๋๊ฑฐ๋
์ฝ๋๋ฅผ ๋ ํจ์จ์ ์ผ๋ก ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ ๋ฑ.
ํ์ฌ๋ ์ฑ๊ธ์ฝ์ด๋ง ์ฌ์ฉํ๋ฏ๋ก, ํด๋ฌ์คํฐ๋ง ๊ธฐ๋ฒ ๋์ ์ ์๋ํด๋ณผ๋ง ํจ
arrivalRate๋ฅผ ์ค์ด๊ฑฐ๋ ๋๋ ค์ ์ด๋ ์ ๋ ์์ฉ ๊ฐ๋ฅํ์ง ์ฒดํฌํด๋ณด๋ ๊ฒ์ด ์ข์
์ฌ๋ฌ ๋ฒ ํ
์คํธํ์ฌ ํ๊ท ์น๋ฅผ ๋ด๋ณด๋ ๊ฒ ์ข์
์ด๋ค ๊ฒ์ ํ ์คํธํ๊ณ ์ด๋ค ๊ฒ์ ํ ์คํธ ์ ํ ์ง ๊ณ ๋ฏผ๋จ.
์์ ์ด ์ง ์ฝ๋๋ ์ต๋ํ ๋ง์ด ํ ์คํธํ๊ธฐ
npm์ ํตํด ์ค์นํ ํจํค์ง๋ ํ ์คํธํ์ง ์์(๊ทธ๊ฑธ ๋ง๋ ์ฌ๋์ ๋ชซ์)
์ฐ๋ฆฌ๋ ๊ทธ ํจํค์ง/๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋ถ๋ถ๋ง ํ ์คํธ
ํ ์คํธํ๊ธฐ ์ด๋ ค์ด ํจํค์ง๋ ๋ชจํน
๋ชจํนํด์ ํต๊ณผํ๋๋ผ๋ ์ค์ ์ํฉ์์๋ ์๋ฌ๋ ์ ์์์ ์ผ๋์ ๋์ด์ผ ํจ
์์คํ ํ ์คํธ: QA์ฒ๋ผ ํ ์คํธ ๋ชฉ๋ก์ ๋๊ณ ์ฒดํฌํด๋๊ฐ๋ฉด์ ์งํํ๋ ํ ์คํธ
์ธ์ ํ ์คํธ: ์ํ ํ ์คํธ/๋ฒ ํ ํ ์คํธ์ฒ๋ผ ํน์ ์ฌ์ฉ์ ์ง๋จ์ด ์ค์ ๋ก ํ ์คํธ
๋ค์ํ ์ข ๋ฅ์ ํ ์คํธ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์ํํด ์๋น์ค๋ฅผ ์์ ์ ์ผ๋ก ์ ์งํ๋ ๊ฒ ์ข์
๐์ถ์ฒ๐
Node.js ๊ต๊ณผ์ - ๊ธฐ๋ณธ๋ถํฐ ํ๋ก์ ํธ ์ค์ต๊น์ง
https://www.inflearn.com/course/%EB%85%B8%EB%93%9C-%EA%B5%90%EA%B3%BC%EC%84%9C/dashboard