Dandy Now!
  • Node.js + MySQL 기반 REST API 통합 테스트 완벽 튜터리얼
    2025년 07월 26일 23시 16분 58초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    Node.js + MySQL 기반 REST API 통합 테스트 완벽 튜터리얼

    1. 통합 테스트란 무엇인가? 왜 필요한가?

    • 통합 테스트(Integration Test)란, 여러 컴포넌트(예: 라우터, 컨트롤러, DB 등)가 실제 환경처럼 잘 연결되어 동작하는지 검증하는 테스트이다.
    • 단위 테스트(Unit Test)는 개별 함수나 모듈만 검증하지만, 통합 테스트는 실제 서버를 띄우고 HTTP 요청을 보내 전체 시스템의 흐름을 검증한다.
    • 필요성
      • 실제 배포 환경과 유사하게 동작하는지 미리 확인할 수 있다.
      • 라우터, 미들웨어, DB 등 여러 계층이 올바르게 연결되어 있는지 한 번에 검증할 수 있다.
      • 예외 상황(없는 데이터, 잘못된 요청 등)도 실제처럼 테스트할 수 있다.

    2. Node.js에서 통합 테스트 구성 방법

    2-1. 필요한 라이브러리

    • express: 서버 프레임워크
    • mysql2: MySQL 데이터베이스 드라이버
    • supertest: HTTP 요청을 시뮬레이션하여 실제 서버에 테스트 요청을 보냄
    • jest: 테스트 러너

    설치 명령어:

    npm install express mysql2 supertest jest

    2-2. MySQL 데이터베이스 준비

    • 테스트용 데이터베이스와 products 테이블을 미리 생성해두자.
    CREATE DATABASE testdb;
    USE testdb;
    CREATE TABLE products (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(255) NOT NULL,
      description TEXT NOT NULL
    );

    3. Node.js + MySQL API 서버 코드

    3-1. DB 연결 설정 (db.js)

    // db.js
    const mysql = require("mysql2/promise");
    
    const pool = mysql.createPool({
      host: "localhost",
      user: "root",
      password: "비밀번호", // 본인 환경에 맞게 수정
      database: "testdb",
      waitForConnections: true,
      connectionLimit: 10,
      queueLimit: 0,
    });
    
    module.exports = pool;

    3-2. Product 모델 함수 (models/Product.js)

    // models/Product.js
    const db = require("../db");
    
    exports.createProduct = async (product) => {
      const [result] = await db.query(
        "INSERT INTO products (name, description) VALUES (?, ?)",
        [product.name, product.description]
      );
      return { id: result.insertId, ...product };
    };
    
    exports.getAllProducts = async () => {
      const [rows] = await db.query("SELECT * FROM products");
      return rows;
    };
    
    exports.getProductById = async (id) => {
      const [rows] = await db.query("SELECT * FROM products WHERE id = ?", [id]);
      return rows[0];
    };
    
    exports.updateProduct = async (id, product) => {
      await db.query("UPDATE products SET name = ?, description = ? WHERE id = ?", [
        product.name,
        product.description,
        id,
      ]);
      return { id, ...product };
    };
    
    exports.deleteProduct = async (id) => {
      await db.query("DELETE FROM products WHERE id = ?", [id]);
    };

    3-3. Express 라우터 및 서버 (controller/products.js, server.js)

    // controller/products.js
    const express = require("express");
    const router = express.Router();
    const Product = require("../models/Product");
    
    router.post("/products", async (req, res) => {
      try {
        const product = await Product.createProduct(req.body);
        res.status(201).json(product);
      } catch (err) {
        res.status(500).json({ message: err.message });
      }
    });
    
    router.get("/products", async (req, res) => {
      const products = await Product.getAllProducts();
      res.json(products);
    });
    
    router.get("/products/:id", async (req, res) => {
      const product = await Product.getProductById(req.params.id);
      if (!product) return res.status(404).json({ message: "Not found" });
      res.json(product);
    });
    
    router.put("/products/:id", async (req, res) => {
      const product = await Product.getProductById(req.params.id);
      if (!product) return res.status(404).json({ message: "Not found" });
      const updated = await Product.updateProduct(req.params.id, req.body);
      res.json(updated);
    });
    
    router.delete("/products/:id", async (req, res) => {
      const product = await Product.getProductById(req.params.id);
      if (!product) return res.status(404).json({ message: "Not found" });
      await Product.deleteProduct(req.params.id);
      res.status(200).json({ message: "Deleted" });
    });
    
    module.exports = router;
    // server.js
    const express = require("express");
    const app = express();
    const productRouter = require("./controller/products");
    
    app.use(express.json());
    app.use("/api", productRouter);
    
    const server = app.listen(3000, () => {
      console.log("Server started");
    });
    
    module.exports = server;

    4. 통합 테스트 코드 작성 (test/integration/products.int.test.js)

    4-1. 테스트 코드 전체 예시

    const request = require("supertest");
    const server = require("../../server");
    const db = require("../../db");
    
    let firstProduct;
    
    beforeAll(async () => {
      // 테스트 시작 전 products 테이블 비우기
      await db.query("DELETE FROM products");
    });
    
    afterAll(async () => {
      await server.close();
      await db.end();
    });
    
    it("POST /api/products - 제품 생성", async () => {
      const newProduct = { name: "테스트상품", description: "설명" };
      const response = await request(server).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("GET /api/products - 전체 제품 조회", async () => {
      const response = await request(server).get("/api/products");
      expect(response.statusCode).toBe(200);
      expect(Array.isArray(response.body)).toBeTruthy();
      firstProduct = response.body[0];
    });
    
    it("GET /api/products/:id - 개별 제품 조회", async () => {
      const response = await request(server).get(
        `/api/products/${firstProduct.id}`
      );
      expect(response.statusCode).toBe(200);
      expect(response.body.name).toBe(firstProduct.name);
    });
    
    it("PUT /api/products/:id - 제품 수정", async () => {
      const response = await request(server)
        .put(`/api/products/${firstProduct.id}`)
        .send({ name: "수정상품", description: "수정설명" });
      expect(response.statusCode).toBe(200);
      expect(response.body.name).toBe("수정상품");
    });
    
    it("DELETE /api/products/:id - 제품 삭제", async () => {
      const response = await request(server).delete(
        `/api/products/${firstProduct.id}`
      );
      expect(response.statusCode).toBe(200);
    });

    4-2. 코드 설명

    • beforeAll: 테스트 시작 전 products 테이블을 비워, 항상 동일한 상태에서 테스트가 시작되도록 한다.

    • afterAll: 테스트가 끝나면 서버와 DB 연결을 종료한다.

    • 각 테스트 케이스

      • POST /api/products: 제품을 생성하고, 응답에 올바른 데이터가 담겼는지 확인
      • GET /api/products: 전체 제품 목록을 배열로 반환하는지 확인
      • GET /api/products/:id: 특정 id의 제품 정보를 정확히 반환하는지 확인
      • PUT /api/products/:id: 제품 정보가 정상적으로 수정되는지 확인
      • DELETE /api/products/:id: 제품이 정상적으로 삭제되는지 확인
    • supertest를 사용하여 실제 서버에 HTTP 요청을 보내고, 응답을 검증한다.
      이로써 라우터, 컨트롤러, DB까지 전체 시스템이 실제 환경처럼 동작하는지 확인할 수 있다.


    5. 마치며

    • 통합 테스트는 실제 서버와 DB가 연동된 환경에서 API가 올바르게 동작하는지,
      그리고 예외 상황도 잘 처리되는지 한 번에 검증할 수 있는 강력한 도구이다.
    • Node.js에서는 supertest, jest, mysql2 등을 활용해 쉽게 통합 테스트 환경을 구축할 수 있다.
    • 실제 서비스에 배포하기 전, 반드시 통합 테스트를 통해 전체 시스템의 신뢰성을 확보하자!

    VSCode Extention Jest Runner(firsttris)를 이용하면 편리한 테스트 환경을 구성할 수 있다.


    728x90
    반응형
    댓글