3. Create 단위 테스트

우동이·2022년 1월 9일
1

NodeServer Test

목록 보기
4/6
post-thumbnail

기본적인 API Create 단위 테스트 순서

  • 데이터 베이스에 Product를 저장하기 위한 함수를 생성하기 전에 먼저 단위 테스트를 진행 ( 예상 )

  • 테스트에 대응하는 실제 코드를 작성

  • npm test로 확인해보기

createProduct 테스트

  • 앞 서 생성한 createProduct() 함수 내부 로직을 검증합니다.

  • 현재 몽구스의 Model을 사용하고 있기 때문에 Model 함수를 테스트 진행합니다.

  • 단위 테스트 작성

import { createProduct } from "../../src/controllers/products";
import { Product } from "../../src/models/product";

// Model mock 함수
// 어떤 것에 의해서 호출되었는지 / 어떤 것과 함께 호출되는지 알 수 있습니다. ( 스파이 역할 )
import { createProduct } from "../../src/controllers/products";
import { Product } from "../../src/models/product";

// Model mock 함수
// 어떤 것에 의해서 호출되었는지 / 어떤 것과 함께 호출되는지 알 수 있습니다. ( 스파이 역할 )
jest.mock("../../src/models/product");

describe("Product Controller Create", () => {

  it("should call ProductModel", () => {
    // createProduct() 함수가 실행 될 때
    createProduct();
    // 내부에서 new Product()가 실행 되는지
    // 여기서 단위테스트 이기 때문에 실제 Model에 영향을 받으면 안되기 떄문에 Mock 함수 사용
    // toBeCalled 함수를 통해 함수가 호출됬는지 체크
    expect(Product).toBeCalled();
  });
});
  • controllers/product.ts ( 실제 코드 작성 )
    • 단위 테스트이기 때문에 실제 DB를 호출하는 것을 방지 하기 위해 위에서 Mock 함수를 활용해 Model를 사용합니다.
    • 직접 DB 호출은 추후 통합 테스트에서 진행합니다.
import { Request, Response, NextFunction } from "express";
import { Product, IProduct } from "../models/product";

export const createProduct = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
    const productModel = new Product();
};

실제 데이터 Create Test

  • 보통 몽구스 모델 등을 사용해서 데이터를 저장할 때 Product.create(req.body)처럼 request body 데이터를 받아와 넣어줍니다.
    • 즉 단위 테스트에서도 Request body data가 필요합니다.
  • 이 때 사용되는 모듈이 node-mock-http 모듈입니다.
    • Http 객체(reqeust, response)를 얻을 수 있습니다.
req = httpMocks.createRequest();
res = httpMocks.createResponse();
  • 테스트 데이터 작성
    • test/data/new-product.json
{
  "name": "컴퓨터",
  "description": "최신 고샤앙 컴퓨터",
  "price": 1500000
}
  • 단위 테스트 작성
import { createProduct } from "../../src/controllers/products";
import { Product } from "../../src/models/product";
import httpMocks from "node-mocks-http";
import newProduct from "../data/new-product.json";

// Model mock 함수
// 어떤 것에 의해서 호출되었는지 / 어떤 것과 함께 호출되는지 알 수 있습니다. ( 스파이 역할 )
jest.mock("../../src/models/product");

describe("Product Controller Create", () => {

  it("should call ProductModel", () => {
    // express controller가 받을 수 있는 인자 생성
    let req = httpMocks.createRequest();
    let res = httpMocks.createResponse();
    let next = null;
    // req.body에 따로 생성한 json data를 넣어줍니다.
    req.body = newProduct;

    // createProduct() 함수가 실행 될 때
     createProduct(req, res, next);
    // 내부에서 new Product()가 실행 되는지
    // 여기서 단위테스트 이기 때문에 실제 Model에 영향을 받으면 안되기 떄문에 Mock 함수 사용
    // toBeCalledWith 함수를 통해 어떤 인자를 받았는지도 함께 검증
    expect(Product).toBeCalledWith(newProduct);
  });
});
  • controllers/product.ts ( 실제 코드 작성 )
import { Request, Response, NextFunction } from "express";
import { Product, IProduct } from "../models/product";

export const createProduct = (req, res, next) => {
 const product: IProduct = req.body;

 const productModel = new Product(product);
};

beforeEach를 통해 공통된 Code 제거

  • 공통적으로 테스트를 진행하기전 우선적으로 특정 작업을 진행할 수 있습니다.
import { createProduct } from "../../src/controllers/products";
import { Product } from "../../src/models/product";
import httpMocks from "node-mocks-http";
import newProduct from "../data/new-product.json";

// Model mock 함수
// 어떤 것에 의해서 호출되었는지 / 어떤 것과 함께 호출되는지 알 수 있습니다. ( 스파이 역할 )
jest.mock("../../src/models/product");


let req: any, res: any, next: any;
// beforeEach() 때문에 모든 테스트 케이스에 똑같이 적용
beforeEach(() => {
  // express controller가 받을 수 있는 인자 생성
  req = httpMocks.createRequest();
  res = httpMocks.createResponse();
  next = null;
});

describe("Product Controller Create", () => {
  beforeEach(() => {
    // req.body에 따로 생성한 json data를 넣어줍니다.
    // beforeEach() 때문에 같은 그룹 모든 케이스에게 똑같이 적용
    req.body = newProduct;
  });

 it("should call ProductModel",  () => {
   
    // createProduct() 함수가 실행 될 때
    createProduct(req, res, next);
    // 내부에서 new Product()가 실행 되는지
    // 여기서 단위테스트 이기 때문에 실제 Model에 영향을 받으면 안되기 떄문에 Mock 함수 사용
    // toBeCalledWith 함수를 통해 어떤 인자를 받았는지도 함께 검증
    expect(Product).toBeCalledWith(newProduct);
  });
});

테스트케이스에서 상태 값 전달하기

  • 단위 테스트 작성
it("should return 201 response code", () => {
    createProduct(req, res, next);
    expect(res.statusCode).toBe(201);
  });
  • controllers/product.ts ( 실제 코드 작성 )
import { Request, Response, NextFunction } from "express";
import { Product, IProduct } from "../models/product";

export const createProduct = (req, res, next) => {
 const product: IProduct = req.body;

 const productModel = new Product(product);
  
 res.status(201)
};

테스트케이스에서 클라이언트로 값 전달하기

  • node-mock-http모듈의 _isEndCalled() 사용
it("should return 201 response code", () => {
    productController.createProduct(req, res, next);
    expect(res.statusCode).toBe(201);
    // res.send() Test
    expect(res._isEndCalled()).toBeTruthy();
  });
  • controllers/product.ts ( 실제 코드 작성 )
import { Request, Response, NextFunction } from "express";
import { Product, IProduct } from "../models/product";

export const createProduct = (req, res, next) => {
 const product: IProduct = req.body;

 const productModel = new Product(product);
  
 res.status(201).json();
};

새로 생성된 데이터를 클라이언트로 전달하기

  • 단위 테스트 작성
    • async / await 추가
    • node-mock-http모듈의 _getJSONData() 사용
      • response 객체에 전달된 JSON 데이터를 참조할 수 있습니다.
    • toStrictEqual()
      • 일반적인 Equal() 보다 더 엄격한 함수
      • undefined를 허용하지 않습니다.
it("should return json body in response", async () => {
    const productModel = new Product(newProduct);
    // mock 함수에 리턴값을 req.body 값으로 설정
    (productModel.save as jest.Mock).mockReturnValue(newProduct);

    await createProduct(req, res, next);
    expect(res._getJSONData()).toStrictEqual(newProduct);
  });
  • controllers/product.ts ( 실제 코드 작성 )
    • async / await 추가
import { Request, Response, NextFunction } from "express";
import { Product, IProduct } from "../models/product";

export const createProduct = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const product: IProduct = req.body;

  const productModel = new Product(product);
  const createdProduct = await productModel.save();

  res.status(201).json(createdProduct);
};

참고

  • 따라하며 배우는 TDD 개발 ( John Ahn )
profile
아직 나는 취해있을 수 없다...

0개의 댓글