Dev/SW Engineering

[TDD] node-mocks-http 모듈, Jest의 beforeEach 사용

HJChung 2021. 2. 24. 05:27

[TDD] TDD 시작하기 (Unit Test, Jest)의 <1) TDD 진행 순서> 와 <2) Mock함수를 사용한 TDD 진행 순서>에서 몽구스 모델을 이용한 Products 데이터베이스 테이블에 product 데이터를 저장(create)할 때 단계적으로 유닛테스트를 진행하면서 구현해보았다. 

그래서, 

 

구현하고자 했던 것은

  1. product 데이터 생성을 위한 함수 작성
  2. 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. 유닛 테스트 작성

  1. 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함수를 사용한다.
  });
});