테스트 코드를 계획 및 작성하는 이유: 사전에 에러를 방지하여 더 높은 품질의 소프트웨어를 제공하기 위함
기능 추가, 버그 수정, 리팩토링을 진행하면서 개발자는 다양한 실수를 할 수 있음
개발자는 직접 의도한 대로 동작하는지 테스트하는 코드를 작성하여
사전에 미리 실수를 잡아내고 코드를 올바르게 수정
테스트를 통해 소프트웨어 개발 전반적인 부분에서 발생할 수 있는 에러를 미리 찾아낸다는 것은
테스트 환경이 실제로 서비스가 배포되는 환경과 일치해야 한다는 것
0. 프로젝트 구조
app.js ,server.js 분리하기
서버 가동에 필요한 모든 코드가 app.js에 포함된 방식이 아닌,
app.js와 server.js로 분리하는 방식으로 변경
app.js
const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const { routes } = require("./src/routes");
const createApp = () => {
const app = express();
app.use(express.json());
app.use(cors());
app.use(morgan("combined"));
app.use(routes);
return app;
};
module.exports = { createApp };
- 애플리케이션의 router, middleware 관련 코드만 남겨두고 app을 리턴하는 createApp 함수를 생성합니다.
const createApp = () => {
const app = express();
app.use(express.json());
app.use(cors());
app.use(morgan("combined"));
app.use(routes);
return app;
};
module.exports = { createApp };
- 테스트 단계에서는 테스트용 request를 활용하기 때문에 따로 listen할 필요 없이 해당 createApp 함수만 활용
server.js
require("dotenv").config();
const { createApp } = require("./app");
const { AppDataSource } = require("./src/models/data-source");
const startServer = async () => {
const app = createApp();
const PORT = process.env.PORT;
await AppDataSource.initialize();
app.listen(PORT, () => {
console.log(`Listening on Port ${PORT}`);
});
};
startServer();
- app은 app.js에서 만들어 놓은 createApp 함수를 활용합니다.
const { createApp } = require('./app');
const { AppDataSource } = require('./src/models/dataSource');
- AppDataSource.initialize() , app.listen() 을 담고 있는 startServer 함수를 실행합니다.
const startServer = async () => {
const app = createApp();
const PORT = process.env.PORT;
await AppDataSource.initialize();
app.listen(PORT, () => {
console.log(`Listening on Port ${PORT}`);
});
};
startServer();
따라서 서버를 켤 때에는 server.js를 실행하고, 테스트 코드에서는 createApp 함수의 리턴 값인 app만 활용합니다.
1. test 폴더에 order.test로 만듦
2. test용 데이터베에스 만들기 _ connection_test
create database [ ]
3. 테스트용 dotenv 설정
테스트를 진행할 때 실제 프로덕션 환경의 데이터베이스를 활용하면 문제가 발생하기 때문에
테스트 전용 환경 변수를 따로 관리
jest를 이용하게 되면
테스트를 실행할 때 다른 환경 변수 파일을 불러와서 DB 커넥션에 활용할 수
따라서 테스트용 dotenv에는 테스트 환경에 맞는 값을 추가해줘야 합니다
1) .env.test , .env.tests.sample 파일 만들고,
2) 데이터베이스 이름 넣기 -> 무조건 connection test database
.env.test에 password 와 database url 자리에 password를 넣어놓고,
.gitignore에는 .env.test 를 넣어둬야 한다.
그래야 원격 저장소에 안 올라가지
3) .gitignore에 .env.test 쓰고,
그 과정에서 생기는 오류는 아래 링크 참고
3. package.json scripts에 명령어를 추가 (jest를 활용하여 테스트를 진행할 때 해당 파일을 config로 테스트할 수 있도록)
"scripts": {
"test": "DOTENV_CONFIG_PATH=.env.test jest --setupFiles=dotenv/config"
},
Server 작동 시에,
package.json을 보면, scripts 에
test , start이 있다.
원래는 nodemon app.js 를 하면, 서버 작동이 됐는데,
저거를 깔고 나서는, npm start 를 하면 된다.
nodemon을 할 거였으면, nodemon server.js 하면 된다.
- test code 실행을 위해서는, npm test --> 에러가 안 날 때까지 하는 거
- 서버 작동은, Npm start
4. message에는 postman에 찍히는 res 값 “message": "Created Orders 주문정보 저장”
/ordes API는 하나여도,
단계별 하는 동작이 다르니
5. 이후 jest를 다운 받아야 한다.
나의 경우는 아래와 같이 되어 있다
6. unit test 작성
그렇다면, 테스트 코드에는 어떤 게 들어가느냐
// tests/user.test.js
// npm i --save-dev supertest
const request = require("supertest");
// supertest의 request에 app을 담아 활용하기 위해 createApp 함수를 불러옵니다.
const { createApp } = require("../app");
// DB와의 커넥션을 위해 DataSource 객체를 불러옵니다.
const { myDataSource } = require("../src/models/data-source");
describe("Sign Up", () => {
let app;
beforeAll(async () => {
// 모든 테스트가 시작하기 전(beforeAll)에 app을 만들고, DataSource를 이니셜라이징 합니다.
app = createApp();
await AppDataSource.initialize();
});
afterAll(async () => {
// 테스트 데이터베이스의 불필요한 데이터를 전부 지워줍니다.
await AppDataSource.query(`TRUNCATE users`);
// 모든 테스트가 끝나게 되면(afterAll) DB 커넥션을 끊어줍니다.
await AppDataSource.destroy();
});
test("FAILED: invalid email", async () => {
// supertest의 request를 활용하여 app에 테스트용 request를 보냅니다.
await request(app)
.post("/users/signup") // HTTP Method, 엔드포인트 주소를 작성합니다.
.send({ email: "wrongEmail", password: "password001@" }) // body를 작성합니다.
.expect(400) // expect()로 예상되는 statusCode, response를 넣어 테스트할 수 있습니다.
.expect({ message: "invalid email!" });
});
// 다음과 같이 본인이 작성한 코드에 맞춰 다양한 케이스를 모두 테스트해야 합니다.
// 그래야 의도에 맞게 코드가 잘 작성되었는지 테스트 단계에서부터 확인할 수 있습니다!
test("SUCCESS: created user", async () => {
await request(app)
.post("/users/signup")
.send({ email: "wecode001@gmail.com", password: "password001@" })
.expect(201);
});
test("FAILED: duplicated email", async () => {
await request(app)
.post("/users/signup")
.send({ email: "wecode001@gmail.com", password: "password001@" })
.expect(409)
.expect({ message: "duplicated email" });
});
});
[추후 복사용 코드 재작성] tests 폴더의 user.test.js 파일
// npm i --save-dev supertest
const request = require("supertest");
// supertest의 request에 app을 담아 활용하기 위해 createApp 함수를 불러옵니다.
const { createApp } = require("../app");
// DB와의 커넥션을 위해 DataSource 객체를 불러옵니다.
const { myDataSource } = require("../src/models/data-source");
describe("Sign Up", () => {
let app;
beforeAll(async () => {
// 모든 테스트가 시작하기 전(beforeAll)에 app을 만들고, DataSource를 이니셜라이징 합니다.
app = createApp();
await AppDataSource.initialize();
});
afterAll(async () => {
// 테스트 데이터베이스의 불필요한 데이터를 전부 지워줍니다.
await AppDataSource.query(`TRUNCATE users`);
// 모든 테스트가 끝나게 되면(afterAll) DB 커넥션을 끊어줍니다.
await AppDataSource.destroy();
});
test("FAILED: invalid email", async () => {
// supertest의 request를 활용하여 app에 테스트용 request를 보냅니다.
await request(app)
.post("/users/signup") // HTTP Method, 엔드포인트 주소를 작성합니다.
.send({ email: "wrongEmail", password: "password001@" }) // body를 작성합니다.
.expect(400) // expect()로 예상되는 statusCode, response를 넣어 테스트할 수 있습니다.
.expect({ message: "invalid email!" });
});
// 다음과 같이 본인이 작성한 코드에 맞춰 다양한 케이스를 모두 테스트해야 합니다.
// 그래야 의도에 맞게 코드가 잘 작성되었는지 테스트 단계에서부터 확인할 수 있습니다!
test("SUCCESS: created user", async () => {
await request(app)
.post("/users/signup")
.send({ email: "wecode001@gmail.com", password: "password001@" })
.expect(201);
});
test("FAILED: duplicated email", async () => {
await request(app)
.post("/users/signup")
.send({ email: "wecode001@gmail.com", password: "password001@" })
.expect(409)
.expect({ message: "duplicated email" });
});
});
8. npm test 명령어를 입력하여 실행
9. 아래와 같은 에러가 pass로 나올 때까지 수정
pass, 성공한 거 보고 싶으면 아래 링크
[에러 해결한 전체코드]
const request = require('supertest'); // 실행은 npm test() --> package.json 활용
// supertest의 request에 app을 담아 활용하기 위해 createApp 함수를 불러옵니다.
const { createApp } = require('../app'); //server.js에 있는 거
// DB와의 커넥션을 위해 DataSource 객체를 불러옵니다.
const { AppDataSource } = require('../src/models/dataSource'); // 활용되는 database 말고, test로 만들어서
// 동작을 describe
describe('make order and pay', () => {
let app;
beforeAll(async () => {
// 모든 테스트가 시작하기 전(beforeAll)에 app을 만들고, DataSource를 이니셜라이징 합니다.
app = createApp();
await AppDataSource.initialize();
// 각 코드 순서 중요 -> data 들어가는 flow 에 맞춰서
// postman에 실제로 req.body에 넣고 통신하기 위해 mysql 테이블에 넣었던 데이터들
// 적을 때는, 하드 코딩해서 직접 값을 key에 입력해주는 것
// users 테이블 채우기
await AppDataSource.query(`
INSERT INTO users (name, email, password, phone_number, zip_code, address, address_details) VALUES ('ddd', 'jdh@naver.com', '111', '010', '00000', '강남구', '101동')
`);
// sellers 채우기
await AppDataSource.query(`
INSERT INTO sellers (name, image, zip_code, address, address_details, phone_number) VALUES ('ddd', 'url', '111', '강남구', '101동 ', '010')
`);
// products 테이블 채우기
await AppDataSource.query(`
INSERT INTO product_categories (category_name) VALUES ('뷰티')
`);
// products 테이블 채우기
await AppDataSource.query(`
INSERT INTO products (id, name, images, price, product_category_id, seller_id) VALUES ( '1', '감자',"url",'1000', 1, 1)
`);
// carts 채우기
await AppDataSource.query(`
INSERT INTO carts (user_id, product_id, quantity) VALUES (1,1,3)
`);
// paymemts 채우기
await AppDataSource.query(`
INSERT INTO payments (method) VALUES ("s")
`);
});
afterAll(async () => {
// truncate: 특정 테이블 비움; 테스트 데이터베이스의 불필요한 데이터를 전부 지워줍니다. -> 그래서 .env 에 test database 넣어야 함
// 위에 table들 여기에 넣음 _ 근데 flow 순서대로 userId > productId > carts> orderId 등
await AppDataSource.query(`SET foreign_key_checks = 0;`); //외래키 비활성화
await AppDataSource.query(`TRUNCATE users`);
await AppDataSource.query(`TRUNCATE sellers`);
await AppDataSource.query(`TRUNCATE product_categories`);
await AppDataSource.query(`TRUNCATE products`);
await AppDataSource.query(`TRUNCATE carts`);
await AppDataSource.query(`TRUNCATE payments`);
await AppDataSource.query(`SET foreign_key_checks = 1;`); // 외래키 다시 활성화
// 모든 테스트가 끝나게 되면(afterAll) DB 커넥션을 끊어줍니다.
await AppDataSource.destroy();
});
test('SUCCESS: created orders ', async () => {
const res = await request(app)
.post('/orders')
.send({
userId: 1,
totalPrice: 1000,
shippingMethod: 'shipping',
paymentId: 1,
products: [
{ productId: 1, quantity: 10 },
{ productId: 2, quantity: 11 },
],
});
expect(res.status).toBe(200);
expect(res.body.message).toEqual('Success');
});
}, 9000);
'Wecode - Project 3 (부트캠프) > Project 3 과정' 카테고리의 다른 글
토큰 담아 req.body 보내기 (0) | 2023.10.22 |
---|---|
Project 3 - [결제 api] 완성 - 포인트 차감 (0) | 2023.10.20 |
Project 3- 주문 api : postman 통신 성공 (if 에러 핸들링 x, transactionx) (0) | 2023.10.20 |
Project 3: [주문 api] - if 에러 핸들링, transaction (0) | 2023.10.20 |
Project 3 : 주문 api postman 통신 방법 (if 에러 핸들링 x, transactionx) + 에러 해결 법 (0) | 2023.10.20 |