[TDD] TDD 시작하기 (Unit Test, Jest)의 <1) TDD 진행 순서> 와 <2) Mock함수를 사용한 TDD 진행 순서>에서 몽구스 모델을 이용한 Products 데이터베이스 테이블에 product 데이터를 저장(create)할 때 단계적으로 유닛테스트를 진행하면서 구현해보았다.
그래서,
구현하고자 했던 것은
- product 데이터 생성을 위한 함수 작성
- createProduct 함수를 호출할 때 Product Model의 Create 메소드가 호출
이었고,
이에 대한 유닛 테스트 코드는
// test/unit/product.test.js
const productController = require('../../controller/products');
const productModel = require('../../models/Product');
//Mock함수 생성
productModel.create = jest.fn();
describe('Product Controller Create', () => {
it('should have a createProduct function', () => {
//product 데이터 생성을 위한 함수가 있는지 여부를 확인하기 위한 테스트 코드
expect(typeof productController.createProduct).toBe('function');
});
it('should call Product.create', () => {
//createProduct 함수를 호출할 때 Product Model의 Create 메소드가 호출이 되는지를 확인 해주기 위한 테스트 코드
productController.createProduct(); //productController.createProduct이 호출 될 때,
expect(productModel.create).toBeCalled(); //productModel.create가 호출되는지
//그런데 이 테스트에서는 productModel에 의존적이어서는 안된다. 그래서 Mock함수를 사용한다.
});
});
이 테스트에 대응하는 실제 코드는
// controller/products.js
const productModel = require('../models/Product');
exports.createProduct = async (req, res, next) => {
productModel.create();
};
까지 진행해 보았다.
몽구스 모델을 이용한 Products 데이터베이스 테이블에 product 데이터를 저장(create)하기 위한 테스트 코드와 실제 코드를 작성해보고자 한다.
데이터를 저장 할 때는 req 객체를 이용해서 요청에 함께 들어온 body 데이터를 create 메소드의 인자로 넣어서 데이터베이스에 저장해주어야 한다. 이 부분에 대한 단위테스트를 진행 할 때도 req 객체가 필요할텐데
유닛 테스트에서 Mock request, response 객체를 얻으려면? node-mocks-http 모듈을 이용한다.
node-mocks-http 모듈
node-mocks-http 모듈이란?
node-mocks-http 모듈은 Express 애플리케이션 라우팅 함수를 테스트하기 위한 Mock 객체를 제공해 준다. 이 모듈을 사용해서 Request 객체와 Response 객체를 얻을 수 있다.
node-mocks-http 모듈을 이용해서 http 객체(request, response)를 얻는 방법
req = httpMocks.createRequest();
res = httpMocks.createResponse();
사용 예시
1. 해야 할 일은?
createProduct 함수를 호출할 때 Product Model의 Create 메소드이 호출 되면서 product 데이터를 저장(create)
2. 유닛 테스트 작성
- node-mocks-http 모듈을 이용해서 http 객체(request, response)를 얻기
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
let next = null;
2. Mock 데이터를 위한 폴더를 만들어서 가짜 데이터 생성
// test/data/new-product.js
{
"name": "Gloves",
"description": "good to wear",
"price": 15
}
3. 앞에서 node-mocks-http 모듈을 통해 얻은 req 객체의 body에 mock 데이터를 넣어준다.
const newProduct = require('../data/new-product.json');
req.body = newProduct;
4. createProduct 함수를 호출할 때 Product Model의 Create 메소드이 호출 되면서 product 데이터와 함께 호출되는지 확인하는 테스트 코드 작성
productController.createProduct(req, res, next); //productController.createProduct이 호출 될 때,
expect(productModel.create).toBeCalledWith(newProduct);
그래서 전체적인 테스트 코드는 아래와 같이 완성된다.
// test/unit/product.test.js
const productController = require('../../controller/products');
const productModel = require('../../models/Product');
const httpMocks = require('node-mocks-http');
const newProduct = require('../data/new-product.json');
//Mock함수 생성
productModel.create = jest.fn();
describe('Product Controller Create', () => {
it('should call Product.create', () => {
//createProduct 함수를 호출할 때 Product Model의 Create 메소드가 호출이 되는지를 확인 해주기 위한 테스트 코드
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
let next = null;
// Mock 데이터 넣어주기
req.body = newProduct;
productController.createProduct(req, res, next); //productController.createProduct이 호출 될 때,
expect(productModel.create).toBeCalledWith(newProduct);
//그런데 이 테스트에서는 productModel에 의존적이어서는 안된다. 그래서 Mock함수를 사용한다.
});
});
※ create 결과에 대한 http 상태코드가 적절하게 반환되는지, 새로 저장된 데이터를 response로 보내주는지에 대한 테스트코드도 필요하다. , 이에 대한 코드는 <더보기> 클릭
it('should return 201 response code', async () => {
await productController.createProduct(req, res, next);
expect(res.statusCode).toBe(201);
expect(res._isEndCalled()).toBeTruthy(); //node-mocks-http모듈에서 제공하는 matcher(toBeTruthy())로, 결과값이 잘 전달되었는지 확인
});
it('should return json body in response', async () => {
//가짜 함수가 어떤 결과값을 반환할 지 mockReturnValue를 사용해서 직접 알려준다.
productModel.create.mockReturnValue(newProduct);
await productController.createProduct(req, res, next);
expect(res._getJSONData()).toStrictEqual(newProduct); //node-mocks-http모듈에서 제공하는 _getJSONData로, 결과값이 잘 전달되었는지 확인
});
3. 테스트에 대응하는 실제 코드 작성
// controller/products.js
const productModel = require('../models/Product');
exports.createProduct = async (req, res, next) => {
try {
const createdProduct = await productModel.create(req.body);
res.status(201).json(createdProduct);
} catch (error) {
next(error);
}
};
Jest의 beforeEach 사용
여러개의 test에 계속 반복되는 코드가 있다면 beforeEach를 사용해서 반복 사용을 줄여 줄 수 있다.
이 beforeEach는 전체 테스트 코드에 적용되는 경우에는 describe들 밖에 선언하고,
특정 describe 내에서만 사용되면 describe 내에 선언,
특정 test(it) 내에서만 사용되면 it 내에 선언 하면 된다.
위의 코드를 beforeEach를 사용하여서 고쳐보면
// test/unit/product.test.js
const productController = require('../../controller/products');
const productModel = require('../../models/Product');
const httpMocks = require('node-mocks-http');
const newProduct = require('../data/new-product.json');
//Mock함수 생성
productModel.create = jest.fn();
//beforeEach
let req, res, next;
beforeEach(() => {
req = httpMocks.createRequest();
res = httpMocks.createResponse();
next = null;
});
describe('Product Controller Create', () => {
beforeEach(() => {
// Mock 데이터 넣어주기
req.body = newProduct;
});
it('should call Product.create', () => {
//createProduct 함수를 호출할 때 Product Model의 Create 메소드가 호출이 되는지를 확인 해주기 위한 테스트 코드
productController.createProduct(req, res, next); //productController.createProduct이 호출 될 때,
expect(productModel.create).toBeCalledWith(newProduct);
//그런데 이 테스트에서는 productModel에 의존적이어서는 안된다. 그래서 Mock함수를 사용한다.
});
});
'Dev > SW Engineering' 카테고리의 다른 글
[TDD] Python Testing Framework 1 - Pytest, unittest (0) | 2021.05.21 |
---|---|
[TDD] 통합 테스트(Integration Test) (0) | 2021.02.26 |
[TDD] TDD 시작하기 (Unit Test, Jest) (0) | 2021.02.22 |
40. Authentication - OAuth 2.0 (0) | 2020.09.14 |
38. Authentication - Express cookie-parser (0) | 2020.09.13 |