단위 테스트에서 request, response 객체를 이용할 수 있게 해주는 모듈이다. createRequest로 컨트롤러가 받을 request를, createResponse로 response 객체를 생성할 수 있다. 이렇게 생성한 객체 안에 원하는 body를 넣는 것도 가능하다.
새로 req와 res 객체를 만든 뒤, 테스트 데이터로 req.body를 채워주고 createProduct를 호출할 때 인자로 제공해주자.
test("should call ProductModel.create", () => {
let req = httpMocks.createRequest()
let res = httpMocks.createResponse()
let next = null
req.body = newProduct
productController.createProduct(req,res,next);
expect(productModel.create).toBeCalledWith(newProduct);
})
toBeCalledWith matcher를 통해서 productModel의 create 함수가 newProduct를 인자로 호출되는지 확인할 수 있다. 테스트를 모두 통과하려면 컨트롤러가 다음과 같이 작성되어야 한다.
exports.createProduct = (req,res,next) =>{
productModel.create(req.body);
}

테스트 여러개를 실행하기 전에 공통적으로 실행하고 싶은 코드가 있다면 beforeEach로 묶어줄 수 있다. 예를 들어 위 코드에서 createProduct의 인자로 넘기기 위하여 req,res,next를 생성하는 부분은 다른 테스트에서도 여러번 사용할 수 있다. describe 밖에서 전역적으로 beforeEach를 작성하면 해당 테스트 파일 안에 있는 test는 모두 적용된다.
let req, res, next
beforeEach(()=>{
req = httpMocks.createRequest()
res = httpMocks.createResponse()
next = null
})
req.body에 newProduct 객체를 넣는 부분은 Products 컨트롤러의 Create 기능을 테스트할 때만 사용된다. 이 부분은 해당 describe 안으로 넣어주자.
describe("Product Controller Create", () =>{
beforeEach(()=>{
req.body = newProduct
})
...
테스트를 실행하면 이전과 같이 모든 테스트가 통과된다.
API 호출 후 상태 값은 res 객체에 statusCode를 toBe matcher로 검사한다. 예를들어 creatProduct는 서버에 데이터를 생성하는 API이므로 201을 반환해야한다.
test("should return 201 status code", () =>{
productController.createProduct(req,res,next)
expect(res.statusCode).toBe(201);
})
컨트롤러에서 임의로 201 코드를 보내주면 테스트를 통과한다.
API 반환값(결과값)이 제대로 전송되는지는 node-mocks-http에서 제공하는 res 객체의 _isEndCalled()로 확인할 수 있다. 이 값이 True면 제대로 반환이 된 것이다.
test("should return 201 status code", () =>{
productController.createProduct(req,res,next)
expect(res.statusCode).toBe(201);
expect(res._isEndCalled()).toBeTruthy()
})
그럼 결과값이 제대로 반환되는지는 어떻게 알 수 있을까. createProduct에서는 Product모델의 create 메서드로 DB에 생성된 데이터 결과값을 보내주어야한다. 당연히 create 함수는 mock 함수이므로 테스트할 때 호출되면 반환할 값을 지정해주어야 한다.
test("should return json body in response",()=>{
productModel.create.mockReturnValue(newProduct)
productController.createProduct(req,res,next)
expect(res._getJSONData()).toStrictEqual(newProduct)
})
mockReturnValue로 mock 함수인 create가 리턴해줄 값을 넣어준 후, 반환값에 담길 json 값은 node-mocks-http의 _getJSONData() 함수를 이용하여 가져올 수 있다. toStrictEqual matcher를 사용하여 이렇게 반환된 값이 newProduct (새로 생성된 객체)와 같은지 확인한다.
exports.createProduct = (req,res,next) =>{
const createdProduct = productModel.create(req.body);
res.status(201).json(createdProduct)
}
컨트롤러는 위와 같이 create 함수의 결과를 json 형태로 반환하도록 작성하면 테스트를 통과한다.
createProduct 함수는 DB에 새로운 row를 추가한다. 이 작업은 당연히 시간이 걸리고, async로 처리해주어야한다. 컨트롤러의 createProduct 함수를 수정해준다.
exports.createProduct = async (req,res,next) =>{
const createdProduct = await productModel.create(req.body);
res.status(201).json(createdProduct)
}
이에 맞춰서 단위테스트도 수정을 해줘야한다. test 속 createProduct 호출부에 async - await 처리를 하면 된다.
test("should return json body in response", async ()=>{
productModel.create.mockReturnValue(newProduct)
await productController.createProduct(req,res,next)
expect(res._getJSONData()).toStrictEqual(newProduct)
})
test에 비동기 처리를 안 해주면 pass가 안되다, 비동기 처리를 해주면 정상적으로 작동한 것을 볼 수 있다.

도중에 vscode의 jest 확장 프로그램을 설치해봤는데 편하다. 테스트 별로 코드줄옆의 아이콘을 눌러서 개별 실행할 수 있고, 테스트 이력도 쉽게 볼 수 있다.
API를 처리하면서 발생할 수 있는 여러 예외를 처리해주어야한다. createProduct의 경우 DB에 접근하는 부분은 mock함수이기 때문에 여기서 발생할 수 있는 에러 메시지도 mock 함수로 처리해주어야한다. 비동기 요청이기 때문에 실패하면 reject에 이 에러 메시지를 담아주면 된다.
const errorMessage = {message: "required proeprty missing"}
const rejectedPromise = Promise.reject(errorMessage)
productModel.create.mockReturnValue(rejectedPromise)
에러 처리를 위한 callback 함수 next가 실행될 때 이 에러메시지가 인자로 호출되어야한다. 이것을 테스트하기 위하여 next도 mock 함수로 생성해준다.
next = jest.fn()
...
await productController.createProduct(req,res,next)
expect(next).toBeCalledWith(errorMessage)
이제 테스트 케이스가 완성되었으니 컨트롤러의 실제 코드도 수정해준다. try-catch 문으로 오류가 발생한 경우 callbak 함수 next를 error와 함께 호출한다.
exports.createProduct = async (req,res,next) =>{
try {
const createdProduct = await productModel.create(req.body);
res.status(201).json(createdProduct)
} catch (error) {
console.error(error);
next(error)
}
}
Products 컨트롤러의 creaetProduct 함수를 테스트하기 위한 단위 테스트 케이스 5가지가 완성되었다.
