본문 바로가기

Node.js/node tdd(test driven development)

TDD - Node 로 TTD하기 2 (단위테스트와 통합테스트)

github.com/loy124/node-express-tdd

 

loy124/node-express-tdd

express에서 jest와 supertest를 활용한 단위 테스트및 통합 테스트 . Contribute to loy124/node-express-tdd development by creating an account on GitHub.

github.com

 

TDD - Node 로 TTD하기 1(개발 준비)

Test Driven Development 테스트 주도 개발은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그

loy124.tistory.com

 

위 글에 이어서 작성하는 내용입니다. 

 

해당 글에서는 create 로직에 대한 단위 테스트및 통합 테스트를 작성합니다. 

단위 테스트 (Unit Test)

개발자가 수행하고 자신이 개발한 코드 단위를 테스트합니다. 소스 코드의 개별 단위를 테스트하여

사용 할 준비가 되었는지 확인하는 테스트 방법

개발 라이프 사이클의 초기 단계에서 버그가 식별되므로 버그 수정비용을 줄이는 데 도움이 된다.

 

 

단위 테스트의 조건

1. 독립적이어야 한다. 어떤 테스트도 다른 테스트에 의존하지 않아야한다.

2. 격리 되어야 한다. Ajax, Axios, LocalStorage등 테스트 대상이 의존하는것을 다른것으로 대체해야한다. 

 

단위 테스트를 하는 이유

1. 프로그램이 크고, 메모리가 많이들거나, DB등이 필요한경우 로컬환경에서 쉽게 코드를 실행시켜보기 어렵기 때문에 유닛테스트를 만들어서 빠르게 자신의 코드가 정상적으로 작동하는지 확인 할 수있다.

 

2. 종속성이 있는 다른 클래스들에서 버그가 나는것을 방지하기 위해서이다.

 

 

Jest

facebook에 의해 만들어진 테스팅 프레임워크

test case를 만들어서 어플리케이션이 잘 돌아가는지 테스트해준다.

 

 

이를 위해 Jest를 위한 setting을 진행해준다.

먼저 package.json을 변경해준다.

scripts의 test부분을 jest로 변경해주었다. 

{
  "name": "tdd-app",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.17.1",
    "jest": "^26.6.3",
    "mongoose": "^5.11.9",
    "node-mocks-http": "^1.9.0",
    "supertest": "^6.0.1"
  }
}

 

jest에서 test파일을 찾는 방법은 다음과같다

  • tests 폴더 안에 있는 모든 파일들
  • {filename}.test.js
  • {filename}.spec.js

 

먼저 테스트를 위한 폴더 구조를 생성해준다.

unit 파트에는 단위 테스트가 

intergration 안에는 통합 테스트가 들어갈 예정이다. 

또한 product.test.js를 생성해준다.

 

해당 products.test.js를 작성하기 전에 Jest 구조에 대해 간략하게 알아본다. 

Jest 구조

describe

여러 관련 테스트드를 그룹화하는 블록을 만든다. 

 

it (test)

개별 테스트를 수행하는곳

각 테스트를 작은 문장처럼 설명한다.

expect

값을 테스트 할 때마다 사용. matcher와 짝을 이루어서 사용된다.

matcher

다른 방법으로 값을 테스트 하도록 matcher를 사용.

 

이를 바탕으로 products.test.js를 작성후 테스트해보겠다.

 

 

test('two plus tow is four ', () => {
    expect(2 + 2).toBe(4);
  //expect(2 + 2).not.toBe(5);
})

 

npm run test

테스트가 통과되었다.

 

이번엔 describe로 묶어서 진행을 해봤다. 

describe("Calculation", () => {
    test('two plus tow is foure ', () => {
        expect(2 + 2).toBe(4);
    });
    test('two plus tow is foure ', () => {
        expect(2 + 2).not.toBe(5);
    });
})

 

 

jest.fn()

Mock함수를 생성하는 함수, mock은 모의를 의미하며 단위테스트를 작성할 때 해당코드가 의존하는 부분을 가짜로 대체해준다.(단위테스트를 독립적으로 유지하게끔 해준다.)

데이터베이스에서 데이터를 삭제하는 코드에 단위테스트를 사용한다고 가정하면 실제 데이터 베이스를 사용하는 경우 문제가 발생할 수 있다. 테스트가 인프라환경에 영향을 받게되므로 좋지 않다.

 

순서 

구현해야할 목록 생각 및 함수 생성 -> 단위테스트 작성 -> 실제 코드 작성 

 

 

Create 단위 테스트하기 

 

product.test.js를 작성해준다.

ProductController에createProduct가 함수인지 파악하는 테스트이다.

describe("Product Controller Create", () => {
    it("should have a createProduct function", () => {
        // ProductController에 createProduct가 함수인지 파악하는것
        // 예상을 하면서 만드는것
        expect(typeof productController.createProduct).toBe("function");
    })
})

정의를 해주지 않았으므로 테스트 오류가 발생한다.

 

이제 controller/product.js에 createProduct를 작성해준다. 

기존의 hello 코드는 지워주었다.

const createProduct = () => {
    
}

module.exports = {createProduct}

 

이를 기반으로 test/unit/products.test.js도 수정해준다

 

const productController = require('../../controller/products');

describe("Product Controller Create", () => {
    it("should have a createProduct function", () => {
        // ProductController에 createProduct가 함수인지 파악하는것
        // 예상을 하면서 만드는것
        expect(typeof productController.createProduct).toBe("function");
    })
})

그후 테스트를 진행해준다. 

 

테스트 성공 

 

 

이제 createProduct함수를 호출 할때 Prodcut Model의 Create 메소드가 호출이 되는지 확인해줘야한다.

 

이제 controller/products.js 를 수정해준다.

 

mongoose를 활용해 Model을 create해주는 부분을 만들어 주었다.

const productModel = require('../models/Product');

const createProduct = () => {
    productModel.create();
}

module.exports = {createProduct}

 

이제 prodcuts.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", () => {
        // ProductController에 createProduct가 함수인지 파악하는것
        // 예상을 하면서 만드는것
        expect(typeof productController.createProduct).toBe("function");
    })
    it("should call ProductModel.create", () => {
        productController.createProduct();
        // productController의 createProduct가 실행될때
        // productModel의 create가 호출되는지 확인
        // DB에 직접적인 영향을 받으면 안되기 때문에 
        // mock함수인 jest.fn()을 확인한다. 
        expect(productModel.create).toBeCalled();
    })
})

단위테스트는 독립적으로 이루어져야 하고 또한 실제 productModel에 영향을 받아 직접 호출하면 안되기 때문에 

jest.fn()을 활용한다. 

 

위와같이 변경해주고 test를 진행해준다.

테스트는 통과하였으나 경고 문구가 발생한다. 

몽구스를 사용할 때 나오는 메세지로써 

jest의 기본 test환경은 jsdom으로 되어있는데 mongoose는 jsdom을 지원하지 않기 때문에 경고 문구가 발생한다.

이에 따라 jest의 기본 test환경을 jsdom을 node로 변경해주면 된다.

 이를 위해 jest.config.js를 작성해준다.

 

 

jest.config.js

module.exports = {
    testEnvironment: "node"
}

 

위와같이 코드를 변경하고 다시 테스트를 진행하면 

 

에러메세지 없이 정상적으로 실행된다.

 

 

node-mocks-http

현재 Product.create()는 아직 저장할 Product 데이터를 넣어주지 않는다. 이제 DB에 저장할 데이터를 넣어준다.

 

원래 몽구스 모델을 이용해서 데이터를 저장 할 때는 

Product.create(req.body) 등으로 http요청으로 함께 들어온 body를 create 메소드에 인자로 넣어줘서 DB에 저장하는데

테스트환경에서는 위와같은 방식으로 진행할 수 없기 때문에 node-mocks-http 라이브러리를 사용한다.  

 

먼저 더미 데이터를 넣는방식을 위해  json형식의 더미데이터를 만들어준다.

data폴더를 생성하고 new-product.json 을생성해줬다.

data/new-product.json

{
    "name": "Chicken",
    "description": "delicious",
    "price": 16000
}

 

이제 controller/prodcut.js 를 수정해준다

 

const productModel = require('../models/Product');

const createProduct = (req, res, next) => {
    productModel.create(req.body);
}

module.exports = {createProduct}

 

이제 이를 기반으로 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 have a createProduct function", () => {
        // ProductController에 createProduct가 함수인지 파악하는것
        // 예상을 하면서 만드는것
        expect(typeof productController.createProduct).toBe("function");
    })
    it("should call ProductModel.create", () => {
        const req = httpMocks.createRequest();
        const res = httpMocks.createResponse();
        const next = null;
        // req.body에 newProduct를 넣어준다.
        req.body = newProduct;
        productController.createProduct(req, res, next);
        // productController의 createProduct가 실행될때
        // productModel의 create가 호출되는지 확인
        // DB에 직접적인 영향을 받으면 안되기 때문에 
        // mock함수인 jest.fn()을 확인한다. 
        expect(productModel.create).toBeCalledWith(newProduct);
    })
})

 

httpMocks를 통해 request와 response를 작성하고 req.body 안에 json으로 생성한 더미데이터를 넣어주었다.

이를 기반으로 테스트가 진행이 된다. 

 

 

테스트가 정상적으로 실행되었다. 

 

beforeEach

여러개의 테스트에 공통된 Code가 잇다면 beforeEach안에 넣어서 반복을 줄여 줄 수 있다. 

 

현재 해당 부분은 앞으로도 중복적으로 사용할 예정이기 때문에 beforeEach로 빼 줄 수 있다.

 

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();
let req, res, next;
beforeEach(() => {
  req = httpMocks.createRequest();
  res = httpMocks.createResponse();
  next = null;
});

describe("Product Controller Create", () => {
  beforeEach(() => {
    req.body = newProduct;
  });
  it("should have a createProduct function", () => {
    // ProductController에 createProduct가 함수인지 파악하는것
    // 예상을 하면서 만드는것
    expect(typeof productController.createProduct).toBe("function");
  });
  it("should call ProductModel.create", () => {
    // req.body에 newProduct를 넣어준다.
    productController.createProduct(req, res, next);
    // productController의 createProduct가 실행될때
    // productModel의 create가 호출되는지 확인
    // DB에 직접적인 영향을 받으면 안되기 때문에
    // mock함수인 jest.fn()을 확인한다.
    expect(productModel.create).toBeCalledWith(newProduct);
  });
});

 

상태값 전달하기 

요청이 성공적으로 처리하는 경우 자원이 생성되었을때 HTTP 응답코드로써 201을 사용한다. 

 

productController의 createProduct가 실제로 성공적으로 동작하게 되면 201 response Code를 반환해야 한다. 

 

이에 따라서 product.test.js에 201을 return하는지 테스트하는 테스트코드를 넣어준다.

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();
let req, res, next;
beforeEach(() => {
  req = httpMocks.createRequest();
  res = httpMocks.createResponse();
  next = null;
});

describe("Product Controller Create", () => {
  beforeEach(() => {
    req.body = newProduct;
  });
  it("should have a createProduct function", () => {
    // ProductController에 createProduct가 함수인지 파악하는것
    // 예상을 하면서 만드는것
    expect(typeof productController.createProduct).toBe("function");
  });
  it("should call ProductModel.create", () => {
    // req.body에 newProduct를 넣어준다.
    productController.createProduct(req, res, next);
    // productController의 createProduct가 실행될때
    // productModel의 create가 호출되는지 확인
    // DB에 직접적인 영향을 받으면 안되기 때문에
    // mock함수인 jest.fn()을 확인한다.
    expect(productModel.create).toBeCalledWith(newProduct);
  });
  // data를 성공적으로 create시 201 
  it("should return 201 response code", () => {
      productController.createProduct(req, res, next);
      expect(res.statusCode).toBe(201);
      expect(res._isEndCalled()).toBeTruthy();
  })
});

k위 코드로 테스트를 하면 200이 return 되어 에러가 발생한다. 따라서 controller/product.js 또한 수정해줘야 한다. 

   

 

 

 

controller/product.js

const productModel = require('../models/Product');

const createProduct = (req, res, next) => {
    productModel.create(req.body);
    res.status(201).send();
}

module.exports = {createProduct}

status code를 201로 만들어주고 test를 진행해 보았다. 

 

 

결과값 전달하기

상태값까지 전달에 성공하였으니 이제 결과값을 전달해주는 코드를 작성하면 된다. 

 

값을 반환해줘야 하기 때문에 controller/product.js 를 수정해준다.

const productModel = require('../models/Product');

const createProduct = (req, res, next) => {
    const createdProduct = productModel.create(req.body);
    res.status(201).json(createdProduct);
}

module.exports = {createProduct}

 

 

mockReturnValue

가짜 함수가 어떠한 결과값을 반환할지 직접 지정해 줄 경우 mockReturnValue를 사용한다. 

 

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();
let req, res, next;
beforeEach(() => {
  req = httpMocks.createRequest();
  res = httpMocks.createResponse();
  next = null;
});

describe("Product Controller Create", () => {
  beforeEach(() => {
    req.body = newProduct;
  });
  it("should have a createProduct function", () => {
    // ProductController에 createProduct가 함수인지 파악하는것
    // 예상을 하면서 만드는것
    expect(typeof productController.createProduct).toBe("function");
  });
  it("should call ProductModel.create", () => {
    // req.body에 newProduct를 넣어준다.
    productController.createProduct(req, res, next);
    // productController의 createProduct가 실행될때
    // productModel의 create가 호출되는지 확인
    // DB에 직접적인 영향을 받으면 안되기 때문에
    // mock함수인 jest.fn()을 확인한다.
    expect(productModel.create).toBeCalledWith(newProduct);
  });
  // data를 성공적으로 create시 201 
  it("should return 201 response code", () => {
      productController.createProduct(req, res, next);
      expect(res.statusCode).toBe(201);
      expect(res._isEndCalled()).toBeTruthy();
  });
  it("should return json body in response", () => {
      productModel.create.mockReturnValue(newProduct);
      productController.createProduct(req, res, next);
    // res의 json data가 newProduct와 일치하는지 판단 여부 
      expect(res._getJSONData()).toStrictEqual(newProduct);
  })
});

 

mock함수로 만든 prodcutModel.create가 mockReturnValue를 통해 new Product로 return하게 만들었고

productController의 createaProduct에 httpMocks로 만든 임의의 req, res, next를 넣어주었다.

해당 res의 JSONdata값이 new Product와 일치하는지 테스트하는 모듈이다. 

 

 

변경후 테스트를 진행해준다.

 

 

 

에러 핸들링하기 

현재 클라이언트 단이 없기 때문에 포스트맨을 사용해서 요청을 임의로 전달하는 용도로 사용한다. 

 

 

www.postman.com/

 

Postman | The Collaboration Platform for API Development

Postman makes API development easy. Our platform offers the tools to simplify each step of the API building process and streamlines collaboration so you can create better APIs faster.

www.postman.com

 

rotues/products.js 를 수정해준다.

 

const express = require('express');
const router = express.Router();
const {createProduct} = require("../controller/products")


router.post('/', createProduct);

module.exports = router;

 

controller/products.js 에 console을 추가해서 값이 잘 넘어오는지 확인해 준다. 

 

res.status값 return을 하도록 변경해주었다. 

const productModel = require('../models/Product');

const createProduct = (req, res, next) => {
    const createdProduct = productModel.create(req.body);
    console.log('createdProduct', createdProduct);
    return res.status(201).json(createdProduct);
}

module.exports = {createProduct}

 

 

 

먼저 서버를 기동시켜준다

 

 

 

이제 postman으로 요청을 보내본다. 

 

 

 

요청 send시 Promise의 pending 상태가 된다. 

 

비동기 처리를 위해 async await를 활용해준다.(비동기코드 -> 동기로)

 

ko.javascript.info/async-await

 

async와 await

 

ko.javascript.info

controller/products.js를 수정해준다

 

 

const productModel = require('../models/Product');

const createProduct = async(req, res, next) => {
    const createdProduct = await productModel.create(req.body);
    console.log('createdProduct', createdProduct);
    return res.status(201).json(createdProduct);
}

module.exports = {createProduct}

 그후 서버를 다시 재 기동하고 요청을 postman으로 보내준다.

 

 

요청이 정상적으로 완료되었다.

 

async await를 실제 코드에도 적용하였다면 test code 또한 async awiat를 적용해줘야한다. 

 

prodcuts.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();
let req, res, next;
beforeEach(() => {
  req = httpMocks.createRequest();
  res = httpMocks.createResponse();
  next = null;
});

describe("Product Controller Create", () => {
  beforeEach(() => {
    req.body = newProduct;
  });
  it("should have a createProduct function", () => {
    // ProductController에 createProduct가 함수인지 파악하는것
    // 예상을 하면서 만드는것
    expect(typeof productController.createProduct).toBe("function");
  });
  it("should call ProductModel.create", async() => {
    // req.body에 newProduct를 넣어준다.
    await productController.createProduct(req, res, next);
    // productController의 createProduct가 실행될때
    // productModel의 create가 호출되는지 확인
    // DB에 직접적인 영향을 받으면 안되기 때문에
    // mock함수인 jest.fn()을 확인한다.
    // expect(productModel.create).toBeCalledWith(req.body);
    expect(productModel.create).toBeCalledWith(newProduct);
  });
  // data를 성공적으로 create시 201 
  it("should return 201 response code", async() => {
      await productController.createProduct(req, res, next);
      expect(res.statusCode).toBe(201);
      expect(res._isEndCalled()).toBeTruthy();
  }); 
  it("should return json body in response", async() => {
      productModel.create.mockReturnValue(newProduct);
      await productController.createProduct(req, res, next);
    // res의 json data가 newProduct와 일치하는지 판단 여부 
      expect(res._getJSONData()).toStrictEqual(newProduct);
  })
});

 

위와같이 코드를 변경하고 다시 test를 진행해준다.

 

 

 

먼저 controller/products.js를 살펴보면 

 

const productModel = require('../models/Product');

const createProduct = async(req, res, next) => {
    const createdProduct = await productModel.create(req.body);
    console.log('createdProduct', createdProduct);
    return res.status(201).json(createdProduct);
}

module.exports = {createProduct}

요청이 정상적으로 오지 않았을 경우에 처리가 되어있지 않다. 따라서 try-catch를 활용해 에러 코드처리법도 나눠준다

 

const productModel = require("../models/Product");

const createProduct = async (req, res, next) => {
  try {
    const createdProduct = await productModel.create(req.body);
    console.log("createdProduct", createdProduct);
    return res.status(201).json(createdProduct);
  } catch (error) {
      next(error);
  }
};

module.exports = { createProduct };

 

 

error 발생시 error 메세지를 next를 통해 넘겨주는 방식이다.

 

이를 바탕으로 testcode를 추가해준다.

 

products.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();
let req, res, next;
beforeEach(() => {
  req = httpMocks.createRequest();
  res = httpMocks.createResponse();
  next = jest.fn();
});

describe("Product Controller Create", () => {
  beforeEach(() => {
    req.body = newProduct;
  });
  it("should have a createProduct function", () => {
    // ProductController에 createProduct가 함수인지 파악하는것
    // 예상을 하면서 만드는것
    expect(typeof productController.createProduct).toBe("function");
  });
  it("should call ProductModel.create", async() => {
    // req.body에 newProduct를 넣어준다.
    await productController.createProduct(req, res, next);
    // productController의 createProduct가 실행될때
    // productModel의 create가 호출되는지 확인
    // DB에 직접적인 영향을 받으면 안되기 때문에
    // mock함수인 jest.fn()을 확인한다.
    // expect(productModel.create).toBeCalledWith(req.body);
    expect(productModel.create).toBeCalledWith(newProduct);
  });
  // data를 성공적으로 create시 201 
  it("should return 201 response code", async() => {
      await productController.createProduct(req, res, next);
      expect(res.statusCode).toBe(201);
      expect(res._isEndCalled()).toBeTruthy();
  }); 
  it("should return json body in response", async() => {
    productModel.create.mockReturnValue(newProduct);
      await productController.createProduct(req, res, next);
    // res의 json data가 newProduct와 일치하는지 판단 여부 
      expect(res._getJSONData()).toStrictEqual(newProduct);
  });
  it("should handle errors", async () => {
      const errorMessage = {message: "description property missing"};
      const rejectedPromise = Promise.reject(errorMessage);
      productModel.create.mockReturnValue(rejectedPromise);
      await productController.createProduct(req, res, next);
      expect(next).toBeCalledWith(errorMessage);
  })
});

next 부분을 jest.fn을 활용해서 mock함수로 넣어줬다. next에 인자를 넣어서 넘겨주면 그 다음 부분의 미들웨어에서 해당 next의 인자를 가지고 처리할 수 있다.

next인자 호출시 errorMessage가 나오는지 확인하는 테스트 코드를 작성해 주었다.

 

테스트 진행시 

 

 

테스트 진행이 완료되었고 CREATE에 대한 단위 테스트는 모두 완료되었다 

 

 

 

통합 테스트

단위 테스트를 먼저 수행하여 모듈들이 잘 작동되는것을 확인했다면 모듈들을 연동해서 수행하는 테스트

 

통합테스트를 하는 이유

모듈들의 상호작용 확인

통합과정의 오류 테스트 

Supertest

nodejs http 서버를 테스트 하기위해 만들어진 모듈

해당 모듈을 활용하면 통합테스트를 쉽게 구현할 수 있다 . 

 

 

통합테스트 예시 

const request = requre("supertest");
const express =r equire("express");


const app = express();

// 원본 소스
app.get('/user', function(req, res) {
	res.status(200).json({name: 'john'});
});

// 원본소스를 위한 통합 테스트 
request(app)
	.get('/user')
    .expect('Content-Type', /json/)
    .expect(200)
    .end(function(err, res){	
    	if(err) throw err;
    });

 

위와 같이 요청을 위한 통합 테스트를 작성해서 실제로 요청을 보내는 테스트를 수행한다

 

 

통합테스트 만들기

 

먼저 server.js에서 사용하고있는 express 모듈을 활용하기위해 module.exports를활용해서 app을 exports해준다

 

server.js

const express = require("express");
const PORT = 5000;
const app = express();
const routes = require("./routes");
const mongoose = require("mongoose");
app.use(express.json());

mongoose
  .connect(
    "mongodb+srv://root1234:root1234@cluster0.ab9f3.mongodb.net/tdd?retryWrites=true&w=majority",
    {
      // 경고 문구 방지
      useNewUrlParser: true,
      useUnifiedTopology: true
    }
  )
  .then(() => console.log("Mongo DB Connected"))
  .catch((err) => console.log(err));
// 해당 요청이 오면 routes/index.js로 보내준다.
app.use("/", routes);

app.listen(PORT, () => {
  console.log(`this server listening on ${PORT}`);
});

module.exports = app;

 

 

 

먼저 integration 폴더에 produts.int.test.js를 작성해준다

 

 

controller/product.js

 

해당 코드를 참고하며 products.int.test를 작성해준다

 

const request = require("supertest");
const app = require("../../server");
const newProduct = require("../data/new-product.json");

it("POST /api/products", async () => {
  const response = await request(app).post("/api/products").send(newProduct);
  expect(response.statusCode).toBe(201);
  expect(response.body.name).toBe(newProduct.name);
  expect(response.body.description).toBe(newProduct.description);
  
});

그후 해당 테스트를 실행해준다.

 

npm run test

 

 

 

 

 

이제 에러를 테스트하는 로직을 작성해준다

 

 

const request = require("supertest");
const app = require("../../server");
const newProduct = require("../data/new-product.json");

it("POST /api/products", async () => {
  const response = await request(app).post("/api/products").send(newProduct);
  expect(response.statusCode).toBe(201);
  expect(response.body.name).toBe(newProduct.name);
  expect(response.body.description).toBe(newProduct.description);
});
// 에러 대응
it("should return 500 on POST /api/products", async () => {
  const response = await request(app)
    .post("/api/products")
    .send({ name: "description 제외하고 보내기" });

  expect(response.statusCode).toBe(500);
  expect(response.body).toStrictEqual({
    message: "Product validation failed",
  });
});

위 상태에서 npm test를 진행하면 error message가 정상적으로 보이지 않게 된다.

 

메세지가 없다.

노드에서 에러가 발생하면 다음 next가 아닌 바로 error 처리기로 보내버리기 때문에 server.js 코드에 대한 수정이 필요하다. 

 

const express = require("express");
const PORT = 5000;
const app = express();
const routes = require("./routes");
const mongoose = require("mongoose");
app.use(express.json());

mongoose
  .connect(
    "mongodb+srv://root1234:root1234@cluster0.ab9f3.mongodb.net/tdd?retryWrites=true&w=majority",
    {
      // 경고 문구 방지
      useNewUrlParser: true,
      useUnifiedTopology: true,
    }
  )
  .then(() => console.log("Mongo DB Connected"))
  .catch((err) => console.log(err));
// 해당 요청이 오면 routes/index.js로 보내준다.
app.use("/", routes);

// 에러 처리기
// 에러가 발생하면 next를 통해 해당 부분으로 넘어 온다.
app.use((error, req, res, next) => {
  res.json({ message: error.message });
});

app.listen(PORT, () => {
  console.log(`this server listening on ${PORT}`);
});

module.exports = app;

 

 

위와 같이 server.js에 app.use를 사용해서 error 처리를 진행해주고 나서 다시 테스트를 진행하면 

 

 

 

이제는 위와같이 메세지가 같이 포함되서 나오게 된다. 

 

마지막으로 message의 내용을 수정해서 나타내주면 된다

 

products.int.test.js

const request = require("supertest");
const app = require("../../server");
const newProduct = require("../data/new-product.json");

it("POST /api/products", async () => {
  const response = await request(app).post("/api/products").send(newProduct);
  expect(response.statusCode).toBe(201);
  expect(response.body.name).toBe(newProduct.name);
  expect(response.body.description).toBe(newProduct.description);
});
// 에러 대응
it("should return 500 on POST /api/products", async () => {
  const response = await request(app)
    .post("/api/products")
    .send({ name: "description 제외" });

  expect(response.statusCode).toBe(500);
  console.log(response.body);
  expect(response.body).toStrictEqual({
    message: "Product validation failed: description: Path `description` is required.",
  });
});

 

 

 

테스트가 정상적으로 수행 되었다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

위 글은

 

www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-tdd/dashboard

해당 강의를 듣고 정리 한 글입니다.