๐Ÿ“‘ ๊ณต์‹ ๋ฌธ์„œ ๋ฒˆ์—ญ/Nest.js

[Nest.js] Fundamentals | Platform agnosticism, Testing

dev_writer 2025. 7. 29. 23:53

ํ”Œ๋žซํผ ๋…๋ฆฝ์„ฑ

Nest๋Š” ํ”Œ๋žซํผ์— ๋…๋ฆฝ์ ์ธ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋กœ์ง์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋Œ€๋ถ€๋ถ„์˜ ์ปดํฌ๋„ŒํŠธ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ HTTP ์„œ๋ฒ„ ํ”„๋ ˆ์ž„์›Œํฌ (์˜ˆ: Express, Fastify)๋Š” ๋ฌผ๋ก , ๋‹ค์–‘ํ•œ ํ˜•ํƒœ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (์˜ˆ: HTTP ์„œ๋ฒ„, ์„œ๋กœ ๋‹ค๋ฅธ ์ „์†ก ๊ณ„์ธต์„ ์‚ฌ์šฉํ•˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค, ์›น์†Œ์ผ“)์—์„œ๋„ ๋ณ€๊ฒฝ ์—†์ด ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•œ ๋ฒˆ ๋งŒ๋“ค๊ณ  ์–ด๋””์„œ๋“  ์‚ฌ์šฉํ•˜์„ธ์š”

๋ฌธ์„œ์˜ Overview ์„น์…˜์—์„œ๋Š” ์ฃผ๋กœ HTTP ์„œ๋ฒ„ ํ”„๋ ˆ์ž„์›Œํฌ (์˜ˆ: REST API ์ œ๊ณต ์•ฑ, MVC ์Šคํƒ€์ผ์˜ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์•ฑ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋”ฉ ๊ธฐ๋ฒ•์„ ๋ณด์—ฌ์ฃผ์ง€๋งŒ, ์ด๋Ÿฌํ•œ ๋ชจ๋“  ๊ตฌ์„ฑ ์š”์†Œ๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค๋‚˜ ์›น์†Œ์ผ“์ฒ˜๋Ÿผ ๋‹ค๋ฅธ ์ „์†ก ๊ณ„์ธต ์œ„์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋˜ํ•œ, Nest๋Š” ์ „์šฉ GraphQL ๋ชจ๋“ˆ์„ ์ œ๊ณตํ•˜์—ฌ REST API ๋Œ€์‹  GraphQL์„ API ๊ณ„์ธต์œผ๋กœ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๋”๋ถˆ์–ด, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด CRON ์ž‘์—…์ด๋‚˜ CLI ์•ฑ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ Nest ์œ„์—์„œ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Nest๋Š” Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ์™„์ „ํ•œ ํ”Œ๋žซํผ์ด ๋˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜๋ฉฐ, ๋†’์€ ์ˆ˜์ค€์˜ ๋ชจ๋“ˆํ™”์™€ ์žฌ์‚ฌ์šฉ์„ฑ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ ๋งŒ๋“ค๊ณ  ์–ด๋””์„œ๋“  ์‚ฌ์šฉํ•˜์„ธ์š”!

ํ…Œ์ŠคํŠธ

์ž๋™ํ™” ํ…Œ์ŠคํŠธ๋Š” ๋ณธ๊ฒฉ์ ์ธ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์—์„œ ํ•„์ˆ˜์ ์ธ ์š”์†Œ๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค. ์ž๋™ํ™”๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœ ์ค‘ ๊ฐœ๋ณ„ ํ…Œ์ŠคํŠธ๋‚˜ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ๋ฅผ ๋น ๋ฅด๊ณ  ์‰ฝ๊ฒŒ ๋ฐ˜๋ณต ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋ฆด๋ฆฌ์Šค๊ฐ€ ํ’ˆ์งˆ ๋ฐ ์„ฑ๋Šฅ ๋ชฉํ‘œ๋ฅผ ์ถฉ์กฑํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž๋™ํ™”๋Š” ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๋†’์ด๊ณ  ๊ฐœ๋ฐœ์ž์—๊ฒŒ ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„๋ฅผ ์ œ๊ณตํ•ด ์ค๋‹ˆ๋‹ค. ์ด๋Š” ๊ฐœ๋ฐœ์ž์˜ ์ƒ์‚ฐ์„ฑ์„ ๋†’์ด๋Š” ๋™์‹œ์—, ์†Œ์Šค ์ฝ”๋“œ ์ปค๋ฐ‹, ๊ธฐ๋Šฅ ํ†ตํ•ฉ, ๋ฒ„์ „ ๋ฆด๋ฆฌ์Šค ๋“ฑ ๊ฐœ๋ฐœ ์ƒ๋ช…์ฃผ๊ธฐ์˜ ์ค‘์š”ํ•œ ์‹œ์ ์— ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

 

์ด๋Ÿฌํ•œ ํ…Œ์ŠคํŠธ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ, E2E (End-to-End) ํ…Œ์ŠคํŠธ ๋“ฑ ๋‹ค์–‘ํ•œ ์œ ํ˜•์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ํšจ๊ณผ๋Š” ๋ถ„๋ช…ํ•˜์ง€๋งŒ, ์ดˆ๊ธฐ ์„ค์ •์€ ๋‹ค์†Œ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Nest๋Š” ํšจ๊ณผ์ ์ธ ํ…Œ์ŠคํŠธ๋ฅผ ํฌํ•จํ•œ ๊ฐœ๋ณ„ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ์žฅ๋ คํ•˜๋ฉฐ, ๊ฐœ๋ฐœ์ž์™€ ํŒ€์ด ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ  ์ž๋™ํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  • ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๋ฐ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ e2e ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™์œผ๋กœ ์Šค์บํด๋”ฉ ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒฉ๋ฆฌ๋œ ๋ชจ๋“ˆ / ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋กœ๋”๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ์™€ ๊ฐ™์€ ๊ธฐ๋ณธ ๋„๊ตฌ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • Jest ๋ฐ Supertest์™€์˜ ํ†ตํ•ฉ์„ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜์ง€๋งŒ, ํŠน์ • ํ…Œ์ŠคํŠธ ๋„๊ตฌ์— ์ข…์†๋˜์ง€ ์•Š๊ณ  ์œ ์—ฐํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • Nest์˜ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ์„ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด, ์ปดํฌ๋„ŒํŠธ ๋ชจํ‚น์ด ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.

์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, Nest๋Š” ํŠน์ • ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๊ฐ•์ œํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์›ํ•˜๋Š” ํ…Œ์ŠคํŠธ ๋„๊ตฌ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ์™€ ๊ฐ™์€ ์š”์†Œ๋งŒ ๊ต์ฒดํ•˜๋ฉด, Nest๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํ…Œ์ŠคํŠธ ์ง€์› ๊ธฐ๋Šฅ์€ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ค์น˜

์‹œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด, ํ•„์ˆ˜ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค.

$ npm i --save-dev @nestjs/testing

๋‹จ์œ„ ํ…Œ์ŠคํŠธ

๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” CatsController์™€ CatsService ๋‘ ํด๋ž˜์Šค๋ฅผ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค. ์•ž์„œ ์–ธ๊ธ‰ํ–ˆ๋“ฏ์ด, Jest๋Š” ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์ œ๊ณต๋˜๋ฉฐ, ํ…Œ์ŠคํŠธ ๋Ÿฌ๋„ˆ ์—ญํ• ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹จ์–ธ ํ•จ์ˆ˜ (assert function), ๋ชจํ‚น ๋ฐ ์ŠคํŒŒ์ด ๋“ฑ์„ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ๋”๋ธ” ์œ ํ‹ธ๋ฆฌํ‹ฐ๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์ด ํด๋ž˜์Šค๋“ค์„ ์ˆ˜๋™์œผ๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๊ณ , ์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค๊ฐ€ API ๊ณ„์•ฝ์„ ์ถฉ์‹คํžˆ ์ดํ–‰ํ•˜๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.

import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

describe('CatsController', () => {
  let catsController: CatsController;
  let catsService: CatsService;

  beforeEach(() => {
    catsService = new CatsService();
    catsController = new CatsController(catsService);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      jest.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});
ํžŒํŠธ
ํ…Œ์ŠคํŠธ ํŒŒ์ผ์€ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค ๊ทผ์ฒ˜์— ์œ„์น˜์‹œํ‚ค๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ํŒŒ์ผ์˜ ์ด๋ฆ„์€ .spec ๋˜๋Š” .test ์ ‘๋ฏธ์‚ฌ๋ฅผ ํฌํ•จํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์œ„์˜ ์˜ˆ์ œ๋Š” ๋‹จ์ˆœํ•œ ํ˜•ํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์— Nest ๊ณ ์œ ์˜ ๊ธฐ๋Šฅ์„ ์‹ค์ œ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ์‹ค์ œ๋กœ ์˜์กด์„ฑ ์ฃผ์ž…๋„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค (์˜ˆ: catsController์— CatsService ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ์ „๋‹ฌํ•จ). ์ด์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค๋ฅผ ์ˆ˜๋™์œผ๋กœ ์ธ์Šคํ„ด์Šคํ™”ํ•˜์—ฌ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ˆ˜ํ–‰ํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ผ๋ฐ˜์ ์œผ๋กœ '๊ฒฉ๋ฆฌ ํ…Œ์ŠคํŠธ (isolated testing)'๋ผ๊ณ  ๋ถ€๋ฆ…๋‹ˆ๋‹ค.

 

์ด์ œ Nest์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ ๊ทน ํ™œ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ

@nestjs/testing ํŒจํ‚ค์ง€๋Š” ๋ณด๋‹ค ๊ฒฌ๊ณ ํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด ์ฃผ๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์•ž์„  ์˜ˆ์ œ๋ฅผ Nest์—์„œ ์ œ๊ณตํ•˜๋Š” Test ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์‹œ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

import { Test } from '@nestjs/testing';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

describe('CatsController', () => {
  let catsController: CatsController;
  let catsService: CatsService;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
        controllers: [CatsController],
        providers: [CatsService],
      }).compile();

    catsService = moduleRef.get(CatsService);
    catsController = moduleRef.get(CatsController);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      jest.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});

 

Test ํด๋ž˜์Šค๋Š” Nest์˜ ์ „์ฒด ๋Ÿฐํƒ€์ž„์„ ๋ชจ์˜ (mock) ํ•˜๋Š” ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ํด๋ž˜์Šค ์ธ์Šคํ„ด์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๊ฑฐ๋‚˜ ๋ชจํ‚น/์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋„์™€์ฃผ๋Š” ํ›…๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ํด๋ž˜์Šค์˜ createTestingModule() ๋ฉ”์„œ๋“œ๋Š” @Module() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์— ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ๊ณผ ๋™์ผํ•œ ๋ชจ๋“ˆ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” TestingModule ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด ์ธ์Šคํ„ด์Šค๋Š” ๋ช‡ ๊ฐ€์ง€ ์œ ์šฉํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

 

๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ ์ค‘์š”ํ•œ ๋ฉ”์„œ๋“œ๋Š” compile()์ž…๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” ์˜์กด์„ฑ์„ ํฌํ•จํ•œ ๋ชจ๋“ˆ์„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•˜๋ฉฐ (์ผ๋ฐ˜์ ์ธ main.ts ์—์„œ NestFactory.create()๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ถ€ํŠธ์ŠคํŠธ๋žฉํ•˜๋Š” ๋ฐฉ์‹๊ณผ ์œ ์‚ฌํ•จ), ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ์˜ ๋ชจ๋“ˆ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

ํžŒํŠธ
compile() ๋ฉ”์„œ๋“œ๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜์ด๋ฏ€๋กœ await ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ์ด ์ปดํŒŒ์ผ๋œ ํ›„์—๋Š”, get() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ๋ชจ๋“ˆ์— ์ •์˜๋œ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ํ”„๋กœ๋ฐ”์ด๋”์™€ ๊ฐ™์€ ์ •์  ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

TestingModule์€ ModuleRef ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›๊ธฐ ๋•Œ๋ฌธ์—, transient ๋˜๋Š” request-scoped์™€ ๊ฐ™์€ ์Šค์ฝ”ํ”„๊ฐ€ ์ง€์ •๋œ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ๋™์ ์œผ๋กœ resolve ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋™์  ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ๋Š” get() ์ด ์•„๋‹Œ resolve() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. get() ๋ฉ”์„œ๋“œ๋Š” ์ •์  ์ธ์Šคํ„ด์Šค๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

const moduleRef = await Test.createTestingModule({
  controllers: [CatsController],
  providers: [CatsService],
}).compile();

catsService = await moduleRef.resolve(CatsService);
์ฃผ์˜
resolve() ๋ฉ”์„œ๋“œ๋Š” ํ•ด๋‹น ํ”„๋กœ๋ฐ”์ด๋”์˜ ๊ณ ์œ ํ•œ DI ์ปจํ…Œ์ด๋„ˆ ์„œ๋ธŒ ํŠธ๋ฆฌ๋กœ๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ์„œ๋ธŒ ํŠธ๋ฆฌ๋Š” ๊ณ ์œ ํ•œ ์ปจํ…์ŠคํŠธ ์‹๋ณ„์ž (context identifier)๋ฅผ ๊ฐ€์ง€๋ฏ€๋กœ, ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๋ฉด ๋งค๋ฒˆ ์„œ๋กœ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ˜ํ™˜๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋น„๊ตํ•˜๋ฉด ์ฐธ์กฐ๊ฐ’์ด ์„œ๋กœ ๋‹ค๋ฆ„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํžŒํŠธ
๋ชจ๋“ˆ ์ฐธ์กฐ ๊ธฐ๋Šฅ์— ๋Œ€ํ•ด ๋” ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ์—ฌ๊ธฐ์—์„œ ํ™•์ธํ•ด ๋ณด์„ธ์š”.

 

์‹ค์ œ ํ”„๋กœ๋ฐ”์ด๋” (Production ์šฉ)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ , ํ…Œ์ŠคํŠธ ๋ชฉ์ ์— ๋งž๊ฒŒ ์ปค์Šคํ…€ ํ”„๋กœ๋ฐ”์ด๋”๋กœ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•˜๋Š” ๋Œ€์‹  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋น„์Šค๋ฅผ ๋ชจํ‚น ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์˜ค๋ฒ„๋ผ์ด๋“œ ๊ธฐ๋Šฅ์€ ๋‹ค์Œ ์„น์…˜์—์„œ ์ž์„ธํžˆ ๋‹ค๋ฃฐ ์˜ˆ์ •์ด์ง€๋งŒ, ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ž๋™ ๋ชจํ‚น

Nest๋Š” ๋ˆ„๋ฝ๋œ ์˜์กด์„ฑ ์ „์ฒด์— ๊ณตํ†ต์ ์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ชจ์˜ (mock) ํŒฉํ† ๋ฆฌ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ ํ…Œ์ŠคํŠธ ๋Œ€์ƒ ํด๋ž˜์Šค์— ๋งŽ์€ ์˜์กด์„ฑ์ด ์žˆ์„ ๋•Œ, ์ผ์ผ์ด ๋ชจํ‚น ํ•˜๋Š” ๋ฐ ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด createTestingModule() ์ดํ›„์— useChecker() ๋ฉ”์„œ๋“œ๋ฅผ ์ฒด์ด๋‹ ํ•˜์—ฌ, ์˜์กด์„ฑ ๋ชจํ‚น์„ ์œ„ํ•œ ํŒฉํ† ๋ฆฌ๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋Š” ์„ ํƒ์ ์œผ๋กœ ํ† ํฐ (token)์„ ์ธ์ž๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด ํ† ํฐ์€ Nest ํ”„๋กœ๋ฐ”์ด๋”๋กœ ์œ ํšจํ•œ ์–ด๋–ค ํ† ํฐ์ด๋“  ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํŒฉํ† ๋ฆฌ ํ•จ์ˆ˜๋Š” ํ•ด๋‹น ํ† ํฐ์— ๋Œ€ํ•œ ๋ชจ์˜ ๊ตฌํ˜„์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” jest-mock์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ๋ฐ˜์ ์ธ mocker๋ฅผ ์ƒ์„ฑํ•˜๊ณ , CatsService์— ๋Œ€ํ•ด์„œ๋Š” jest.fn()์„ ํ™œ์šฉํ•œ ํŠน์ • ๋ชจํ‚น์„ ์ ์šฉํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

// ...
import { ModuleMocker, MockMetadata } from 'jest-mock';

const moduleMocker = new ModuleMocker(global);

describe('CatsController', () => {
  let controller: CatsController;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [CatsController],
    })
      .useMocker((token) => {
        const results = ['test1', 'test2'];
        if (token === CatsService) {
          return { findAll: jest.fn().mockResolvedValue(results) };
        }
        if (typeof token === 'function') {
          const mockMetadata = moduleMocker.getMetadata(
            token,
          ) as MockMetadata<any, any>;
          const Mock = moduleMocker.generateFromMetadata(
            mockMetadata,
          ) as ObjectConstructor;
          return new Mock();
        }
      })
      .compile();

    controller = moduleRef.get(CatsController);
  });
});

 

์ด๋Ÿฌํ•œ ๋ชจํ‚น ๋œ ์ธ์Šคํ„ด์Šค๋“ค๋„ ์ผ๋ฐ˜ ์ปค์Šคํ…€ ํ”„๋กœ๋ฐ”์ด๋”์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ…Œ์ŠคํŠธ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊บผ๋‚ด์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, moduleRef.get(CatsService)์™€ ๊ฐ™์ด ํ˜ธ์ถœํ•˜๋ฉด ํ•ด๋‹น ๋ชจํ‚น๋œ CatsService ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํžŒํŠธ
@golevelup/ts-jest์˜ createMock๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜์ ์ธ ๋ชจ์˜ (mock) ํŒฉํ† ๋ฆฌ๋„ useMocker()์— ์ง์ ‘ ์ „๋‹ฌํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํžŒํŠธ
REQUEST์™€ INQUIRER ํ”„๋กœ๋ฐ”์ด๋”๋Š” Nest ์ปจํ…์ŠคํŠธ ๋‚ด์— ์ด๋ฏธ ์‚ฌ์ „ ์ •์˜๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž๋™ ๋ชจํ‚น์ด ๋ถˆ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์ปค์Šคํ…€ ํ”„๋กœ๋ฐ”์ด๋” ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ .overrideProvider ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

End to End ํ…Œ์ŠคํŠธ

๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐœ๋ณ„ ๋ชจ๋“ˆ์ด๋‚˜ ํด๋ž˜์Šค์— ์ดˆ์ ์„ ๋งž์ถ˜ ๋ฐ˜๋ฉด, E2E (End-to-End) ํ…Œ์ŠคํŠธ๋Š” ํด๋ž˜์Šค์™€ ๋ชจ๋“ˆ ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋” ํฐ ์ˆ˜์ค€์—์„œ ๊ฒ€์ฆํ•˜๋ฉฐ, ์ด๋Š” ์‹ค์ œ ์‚ฌ์šฉ์ž๊ฐ€ ํ”„๋กœ๋•์…˜ ์‹œ์Šคํ…œ๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๋ฐฉ์‹์— ๋” ๊ฐ€๊น์Šต๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ปค์งˆ์ˆ˜๋ก ๊ฐ API ์—”๋“œํฌ์ธํŠธ์˜ E2E ๋™์ž‘์„ ์ˆ˜๋™์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ๊ฒƒ์€ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ์ž๋™ํ™”๋œ E2E ํ…Œ์ŠคํŠธ๋Š” ์‹œ์Šคํ…œ์˜ ์ „์ฒด์ ์ธ ๋™์ž‘์ด ์˜ฌ๋ฐ”๋ฅด๋ฉฐ ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. E2E ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ๋•Œ๋Š” ์•ž์„œ ์‚ดํŽด๋ณธ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ๊ณผ ์œ ์‚ฌํ•œ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, Nest๋Š” Supertest ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•ด HTTP ์š”์ฒญ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•˜๋Š” ์ž‘์—…๋„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { CatsModule } from '../../src/cats/cats.module';
import { CatsService } from '../../src/cats/cats.service';
import { INestApplication } from '@nestjs/common';

describe('Cats', () => {
  let app: INestApplication;
  let catsService = { findAll: () => ['test'] };

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [CatsModule],
    })
      .overrideProvider(CatsService)
      .useValue(catsService)
      .compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it(`/GET cats`, () => {
    return request(app.getHttpServer())
      .get('/cats')
      .expect(200)
      .expect({
        data: catsService.findAll(),
      });
  });

  afterAll(async () => {
    await app.close();
  });
});
ํžŒํŠธ
Fastify๋ฅผ HTTP ์–ด๋Œ‘ํ„ฐ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์•ฝ๊ฐ„ ๋‹ค๋ฅธ ์„ค์ •์ด ํ•„์š”ํ•˜๋ฉฐ ์ž์ฒด์ ์ธ ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ๋„ ๋‚ด์žฅ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
let app: NestFastifyApplication;

beforeAll(async () => {
  app = moduleRef.createNestApplication<NestFastifyApplication>(
    new FastifyAdapter(),
  );

  await app.init();
  await app.getHttpAdapter().getInstance().ready();
});

it(`/GET cats`, () => {
  return app
    .inject({
      method: 'GET',
      url: '/cats',
    })
    .then((result) => {
      expect(result.statusCode).toEqual(200);
      expect(result.payload).toEqual(/* expectedPayload */);
    });
});

afterAll(async () => {
  await app.close();
});โ€‹

 

์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” ์•ž์—์„œ ์„ค๋ช…ํ•œ ๊ฐœ๋…๋“ค์„ ํ™•์žฅํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด์ „์— ์‚ฌ์šฉํ–ˆ๋˜ compile() ๋ฉ”์„œ๋“œ์— ๋”ํ•ด, ์ด๋ฒˆ์—๋Š” createNestApplication() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ „์ฒด Nest ๋Ÿฐํƒ€์ž„ ํ™˜๊ฒฝ์„ ์ธ์Šคํ„ด์Šคํ™”ํ•ฉ๋‹ˆ๋‹ค.

 

์ฃผ์˜ํ•  ์ ์€, compile() ๋ฉ”์„œ๋“œ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ปดํŒŒ์ผํ•  ๋•Œ๋Š” ์•„์ง HTTP ์–ด๋Œ‘ํ„ฐ๋‚˜ ์„œ๋ฒ„๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— HttpAdapterHost#httpAdapter ๊ฐ’์ด undefined ์ƒํƒœ๋ผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ…Œ์ŠคํŠธ ์ค‘ httpAdapter๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด createNestApplication()์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜๋Š” ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ๋•Œ ํ•ด๋‹น ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜์ง€ ์•Š๋„๋ก ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ฅผ ๋ฆฌํŒฉํ„ฐ๋ง ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์ข‹์Šต๋‹ˆ๋‹ค. ์˜ˆ์ œ๋ฅผ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ด…์‹œ๋‹ค:

 

์šฐ๋ฆฌ๋Š” ์‹คํ–‰ ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ app ๋ณ€์ˆ˜์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด HTTP ์š”์ฒญ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

Supertest์˜ request() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HTTP ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ HTTP ์š”์ฒญ์ด ์‹คํ–‰ ์ค‘์ธ Nest ์•ฑ์œผ๋กœ ๋ผ์šฐํŒ… ๋˜๋„๋ก ํ•˜๋ ค๋ฉด, Nest์˜ ๊ธฐ๋ฐ˜์ด ๋˜๋Š” HTTP ๋ฆฌ์Šค๋„ˆ (Express ํ”Œ๋žซํผ์—์„œ ์ œ๊ณต๋  ์ˆ˜ ์žˆ์Œ)์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ request()์— ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ request(app.getHttpServer())๋ผ๋Š” ๊ตฌ๋ฌธ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. request() ํ˜ธ์ถœ์€ Nest ์•ฑ์— ์—ฐ๊ฒฐ๋œ HTTP ์„œ๋ฒ„๋ฅผ ๊ฐ์‹ผ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์‹ค์ œ HTTP ์š”์ฒญ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, request(...).get('/cats')๋ผ๋Š” Nest ์•ฑ์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ์‹ค์ œ GET /cats ์š”์ฒญ๊ณผ ๋™์ผํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

 

์ด ์˜ˆ์ œ์—์„œ๋Š” CatsService์˜ ๋Œ€์ฒด (ํ…Œ์ŠคํŠธ์šฉ) ๊ตฌํ˜„๋„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์„ค์ •๋˜๋ฉฐ, ํ…Œ์ŠคํŠธ์—์„œ ๊ทธ ๊ฐ’์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ๋Œ€์ฒด ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๋ ค๋ฉด overrideProvider()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Nest๋Š” ์ด์™€ ์œ ์‚ฌํ•˜๊ฒŒ overrideModule(), overrideGuard(), overrideInterceptor(), overrideFilter(), overridePipe() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ชจ๋“ˆ, ๊ฐ€๋“œ, ์ธํ„ฐ์…‰ํ„ฐ, ํ•„ํ„ฐ, ํŒŒ์ดํ”„๋„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

 

์ด๋Ÿฌํ•œ override ๋ฉ”์„œ๋“œ๋“ค (๋‹จ, overrideModule() ์ œ์™ธ)์€ ์‚ฌ์šฉ์ž ์ •์˜ ๊ณต๊ธ‰์ž ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด ์„ค๋ช…๋œ ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜์˜ํ•˜๋Š” 3๊ฐ€์ง€ ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • useClass: ์ง€์ •ํ•œ ํด๋ž˜์Šค๋ฅผ ์ธ์Šคํ„ด์Šคํ™”ํ•˜์—ฌ ์˜ค๋ธŒ์ ํŠธ (ํ”„๋กœ๋ฐ”์ด๋”, ๊ฐ€๋“œ ๋“ฑ)๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
  • useValue: ์ง€์ •ํ•œ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์—ฌ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
  • useFactory: ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•˜์—ฌ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

๋ฐ˜๋ฉด, overrideModule()์€ useModule() ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๊ธฐ์กด ๋ชจ๋“ˆ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒˆ๋กœ์šด ๋ชจ๋“ˆ๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const moduleRef = await Test.createTestingModule({
  imports: [AppModule],
})
  .overrideModule(CatsModule)
  .useModule(AlternateCatsModule)
  .compile();

 

๊ฐ override ๋ฉ”์„œ๋“œ๋Š” ๊ฒฐ๊ตญ TestingModule ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, ๋‹ค๋ฅธ ๋ฉ”์„œ๋“œ๋“ค๊ณผ ์ฒด์ด๋‹ (fluent style)์œผ๋กœ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์ฒด์ธ์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋งˆ์ง€๋ง‰์— compile()์„ ํ˜ธ์ถœํ•˜์—ฌ Nest๊ฐ€ ๋ชจ๋“ˆ์„ ์ธ์Šคํ„ด์Šคํ™”ํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

๋˜ํ•œ, ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ (์˜ˆ: CI ์„œ๋ฒ„์—์„œ) ์ปค์Šคํ…€ ๋กœ๊ฑฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๋•Œ๋Š” setLogger() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , LoggerService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ์กฑํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ค‘ ๋กœ๊น… ๋ฐฉ์‹์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” "error" ๋ ˆ๋ฒจ์˜ ๋กœ๊ทธ๋งŒ ์ฝ˜์†”์— ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.)

 

์ปดํŒŒ์ผ๋œ ๋ชจ๋“ˆ์€ ๋‹ค์Œ ํ‘œ์— ์„ค๋ช…๋œ ์—ฌ๋Ÿฌ ์œ ์šฉํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:

createNestApplication() ์ฃผ์–ด์ง„ ๋ชจ๋“ˆ์„ ๊ธฐ๋ฐ˜์œผ๋กœ Nest ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (INestApplication ์ธ์Šคํ„ด์Šค)์„ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. init() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ˆ˜๋™์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”.
createNestMicroservice() ์ฃผ์–ด์ง„ ๋ชจ๋“ˆ์„ ๊ธฐ๋ฐ˜์œผ๋กœ Nest ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค (INestMicroservice ์ธ์Šคํ„ด์Šค)๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
get() ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปจํŠธ๋กค๋Ÿฌ ๋˜๋Š” ํ”„๋กœ๋ฐ”์ด๋” (๊ฐ€๋“œ, ํ•„ํ„ฐ ๋“ฑ ํฌํ•จ)์˜ ์ •์  ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ ์ฐธ์กฐ ํด๋ž˜์Šค์—์„œ ์ƒ์†๋ฉ๋‹ˆ๋‹ค.
resolve() ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปจํŠธ๋กค๋Ÿฌ ๋˜๋Š” ํ”„๋กœ๋ฐ”์ด๋” (๊ฐ€๋“œ, ํ•„ํ„ฐ ๋“ฑ ํฌํ•จ)์˜ ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ์Šค์ฝ”ํ”„ ์ธ์Šคํ„ด์Šค (request ๋˜๋Š” transient)๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“ˆ ์ฐธ์กฐ ํด๋ž˜์Šค์—์„œ ์ƒ์†๋ฉ๋‹ˆ๋‹ค.
select() ๋ชจ๋“ˆ์˜ ์˜์กด์„ฑ ๊ทธ๋ž˜ํ”„๋ฅผ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ํŠน์ • ๋ชจ๋“ˆ์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋ฉฐ, get() ๋ฉ”์„œ๋“œ์˜ strict ๋ชจ๋“œ (strict: true)์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
ํžŒํŠธ
E2E ํ…Œ์ŠคํŠธ ํŒŒ์ผ์€ test ๋””๋ ‰ํ„ฐ๋ฆฌ ์•ˆ์— ๋‘์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ํŒŒ์ผ์˜ ์ด๋ฆ„์€ .e2e-spec ์ ‘๋ฏธ์‚ฌ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ „์—ญ์œผ๋กœ ๋“ฑ๋ก๋œ ํ–ฅ์ƒ์ž (Enhancer) ์žฌ์ •์˜ํ•˜๊ธฐ

์ „์—ญ์œผ๋กœ ๋“ฑ๋ก๋œ ๊ฐ€๋“œ (๋˜๋Š” ํŒŒ์ดํ”„, ์ธํ„ฐ์…‰ํ„ฐ, ํ•„ํ„ฐ)๋ฅผ ์žฌ์ •์˜ํ•˜๋ ค๋ฉด ๋ช‡ ๊ฐ€์ง€ ์ถ”๊ฐ€ ๋‹จ๊ณ„๋ฅผ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์›๋ž˜์˜ ๋“ฑ๋ก ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

providers: [
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
],

 

์ด ์ฝ”๋“œ๋Š” APP_* ํ† ํฐ์„ ํ†ตํ•ด ๊ฐ€๋“œ๋ฅผ "๋ฉ€ํ‹ฐ" ํ”„๋กœ๋ฐ”์ด๋”๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ JwtAuthGuard๋ฅผ ๊ต์ฒดํ•˜๋ ค๋ฉด, ํ•ด๋‹น ์œ„์น˜์— ๊ธฐ์กด ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

providers: [
  {
    provide: APP_GUARD,
    useExisting: JwtAuthGuard,
    // ^^^^^^^^ 'useClass' ๋Œ€์‹  'useExisting'์„ ์‚ฌ์šฉํ•œ ์ ์— ์ฃผ๋ชฉํ•˜์„ธ์š”
  },
  JwtAuthGuard,
],
ํžŒํŠธ
Nest๊ฐ€ ํ† ํฐ ๋’ค์—์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๋„๋ก ํ•˜๋ ค๋ฉด useClass ๋Œ€์‹  useExisting์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ ๋“ฑ๋ก๋œ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

 

์ด์ œ JwtAuthGuard๋Š” Nest์— ์ผ๋ฐ˜ ํ”„๋กœ๋ฐ”์ด๋”๋กœ ์ธ์‹๋˜๋ฏ€๋กœ, TestingModule์„ ์ƒ์„ฑํ•  ๋•Œ ์žฌ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

const moduleRef = await Test.createTestingModule({
  imports: [AppModule],
})
  .overrideProvider(JwtAuthGuard)
  .useClass(MockAuthGuard)
  .compile();

 

์ด์ œ ๋ชจ๋“  ํ…Œ์ŠคํŠธ์—์„œ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด MockAuthGuard๊ฐ€ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์š”์ฒญ ๋ฒ”์œ„ ์ธ์Šคํ„ด์Šค ํ…Œ์ŠคํŠธํ•˜๊ธฐ

์š”์ฒญ ๋ฒ”์œ„ ํ”„๋กœ๋ฐ”์ด๋”๋Š” ๊ฐ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ๋งˆ๋‹ค ๊ณ ์œ ํ•˜๊ฒŒ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ์ธ์Šคํ„ด์Šค๋Š” ์š”์ฒญ ์ฒ˜๋ฆฌ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๋ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ, ํ…Œ์ŠคํŠธ๋œ ์š”์ฒญ์„ ์œ„ํ•ด ํŠน๋ณ„ํžˆ ์ƒ์„ฑ๋œ ์˜์กด์„ฑ ์ฃผ์ž… ํ•˜์œ„ ํŠธ๋ฆฌ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

 

์œ„์—์„œ ์„ค๋ช…ํ•œ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ, resolve() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋™์ ์œผ๋กœ ์ธ์Šคํ„ด์Šคํ™”๋œ ํด๋ž˜์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์—ฌ๊ธฐ์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ์ฒ˜๋Ÿผ ๊ณ ์œ ํ•œ ์ปจํ…์ŠคํŠธ ์‹๋ณ„์ž๋ฅผ ์ „๋‹ฌํ•˜์—ฌ DI ์ปจํ…Œ์ด๋„ˆ ํ•˜์œ„ ํŠธ๋ฆฌ์˜ ์ƒ๋ช… ์ฃผ๊ธฐ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ์š”?

 

์ „๋žต์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์‚ฌ์ „์— ์ปจํ…์ŠคํŠธ ์‹๋ณ„์ž๋ฅผ ์ƒ์„ฑํ•œ ํ›„, Nest๊ฐ€ ๋ชจ๋“  ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์— ๋Œ€ํ•ด ์ด ์‹๋ณ„์ž๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๊ฐ•์ œํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธ๋œ ์š”์ฒญ์— ๋Œ€ํ•ด ์ƒ์„ฑ๋œ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

์ด๋ฅผ ์œ„ํ•ด ContextIdFactory์— ๋Œ€ํ•ด jest.spyOn()์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

const contextId = ContextIdFactory.create();
jest
  .spyOn(ContextIdFactory, 'getByRequest')
  .mockImplementation(() => contextId);

 

์ด์ œ contextId๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ดํ›„์˜ ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ƒ์„ฑ๋œ DI ์ปจํ…Œ์ด๋„ˆ ํ•˜์œ„ ํŠธ๋ฆฌ ํ•˜๋‚˜์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

catsService = await moduleRef.resolve(CatsService, contextId);

Reference