๋ณธ ๊ธ€์€ ์ œ๊ฐ€ NestJS ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊นจ๋‹ฌ์€ ๋…ธํ•˜์šฐ๋ฅผ ๊ธฐ๋กํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์ œ์‹œํ•œ ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€์ ์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค :)

11 minute read

๋ณธ ๊ธ€์€ ์ œ๊ฐ€ NestJS ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ†ตํ•ด ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ๊นจ๋‹ฌ์€ ๋…ธํ•˜์šฐ๋ฅผ ๊ธฐ๋กํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์ œ์‹œํ•œ ๋ฐฉ๋ฒ•๋ณด๋‹ค ๋” ์ข‹์€ ๋ฐฉ๋ฒ•์ด ์žˆ์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง€์ ์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค :)




NestJS์—์„  Javascript์˜ ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ์ธ jestlink๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ํ•˜๋Š” ํ…Œ์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ง€์›ํ•œ๋‹ค.

๋ฌผ๋ก  ์•ฝ๊ฐ„์˜ ๋ณ€ํ˜•์€ ์žˆ๊ฒ ์ง€๋งŒ, ๊ทธ๋ƒฅ ์ง€์›๋งŒ ํ•˜๋Š” ์ˆ˜์ค€์ด ์•„๋‹ˆ๋ผ NestJS์˜ ํ…Œ์ŠคํŠธ๋ฅผ jest๋กœ ํ•œ๋‹ค.

npm i --save-dev @nestjs/testing


ํ…Œ์ŠคํŒ… ๊ธฐ์ดˆ ์ฝ”๋“œ

NestJS CLI๋ฅผ ์ด์šฉํ•ด NestJS Object๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋˜๋ฉด ์ž๋™์œผ๋กœ ํ…Œ์ŠคํŒ… ํŒŒ์ผ์ธ .spec.ts๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

์ด .sepc.ts ํŒŒ์ผ์€ Controller์™€ service๋ฅผ NestJS CLI๋กœ ์ƒ์„ฑํ•  ๋•Œ์—๋งŒ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค.

์‚ฌ์‹ค ๋‘˜์˜ ์ฐจ์ด๋Š” ๊ฑฐ์˜ ์—†๋Š”๋ฐ, ๋งŒ์•ฝ ์ฐจ์ด๋ฅผ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด ํŽผ์ณ๋ณด๊ธฐ์— ๊ธฐ์ˆ ์€ ํ•ด๋‘๊ฒ ๋‹ค.

controller.spec.ts vs. serivice.spec.ts


  • app.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatController } from './cat.controller';
import { CatService } from './cat.service';

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

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [CatController],
      service: [CatService]
    }).compile();

    controller = module.get<CatController>(CatController);
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });
});
  • app.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatService } from './cat.service';

describe('CatService', () => {
  let service: CatService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [CatService],
    }).compile();

    service = module.get<CatService>(CatService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});

์ •๋ง ์ฐจ์ด๊ฐ€ ์—†์ง€ ์•Š์€๊ฐ€? ๐Ÿ™‚


์‚ฌ์‹ค ๋‘˜์˜ ์ฐจ์ด๊ฐ€ ๊ฑฐ์˜ ์—†๊ณ , ๋‹ค๋ฅธ ํฌ์ŠคํŠธ๋ฅผ ์ฐพ์•„๋ด๋„ ๋ณดํ†ต Controller๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด์„œ ์—ฌ๊ธฐ์—์„œ๋„ controller๋ฅผ ๊ธฐ์ค€์œผ๋กœ controller.spec.ts๋ฅผ ์ž‘์„ฑํ•ด๋ณด๊ฒ ๋‹ค.

NestJS App์„ ์ƒ์„ฑํ•  ๋•Œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” app.controller.spec.ts ํŒŒ์ผ์ด๋‹ค. ์šฐ์„  ์ด ๋…€์„์„ ๋„ํ•ด(ๅœ–่งฃ)ํ•ด๋ณด์ž.

import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  let appController: AppController;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      controllers: [AppController],
      providers: [AppService],
    }).compile();

    appController = app.get<AppController>(AppController);
  });

  describe('root', () => {
    it('should return "Hello World!"', () => {
      expect(appController.getHello()).toBe('Hello World!');
    });
  });
});

jestjs๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ describe ๋‚ด๋ถ€์— ์ •์˜ํ•œ๋‹ค.

์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š” describe('AppController', ...)์™€ ๋‚ด๋ถ€์— describe('root', ...)๊ฐ€ ์ •์˜๋˜์–ด ์žˆ๋‹ค.

describe()์˜ ์ธ์ž๋กœ ๋“ค์–ด๊ฐ€๋Š” String์€ ๋‹จ์ˆœํžˆ Testing์„ ์ •์˜ํ•˜๋Š” ์ด๋ฆ„์— ๋ถˆ๊ณผํ•˜๋‹ค. ํ…Œ์ŠคํŠธ ๋กœ์ง์—๋Š” ์•„๋ฌด ๊ด€๊ณ„๊ฐ€ ์—†๋‹ค.

beforeEach()์—๋Š” ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ์‹คํ–‰ ์ด์ „์— ์„ ํ–‰ํ•  ๋‚ด์šฉ์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค.

์œ„์˜ ์ฝ”๋“œ๋Š” TestingModule์ธ app์„ ์ƒ์„ฑํ•œ๋‹ค.


์‹ค์ œ ํ…Œ์ŠคํŒ… ์ฝ”๋“œ๋Š” it()์—์„œ ์ •์˜๋œ๋‹ค. it()์—๋„ String ์ธ์ž๊ฐ€ ๋“ค์–ด๊ฐ€๋Š”๋ฐ, describe()์˜ ๊ทธ๊ฒƒ์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์„ ์ •์˜ํ•˜๋Š” ์ด๋ฆ„์ด๋ผ๋ฉด, it()์˜ String์€ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์˜๋ฏธํ•œ๋‹ค.


๊ทธ๋ž˜์„œ ์š”์•ฝํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค!

describe('test title', () => {
  it('test description', () => {
    expect("value-wanting-to-test").tobe("value-wanting-to-get")
  }
})



๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ŠคํŠธ

Jest ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž!

MockRepository

์•ž์˜ ์ƒํ™ฉ์€ ๋ฌธ์ž์—ด ๋น„๊ต ์ˆ˜์ค€์˜ ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŒ…์ด์ง€๋งŒ, ์‹ค์ œ ์„œ๋ฒ„๋ฅผ ํ…Œ์ŠคํŒ…ํ•˜๊ธฐ ์œ„ํ•ด์„  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜๋Š” API๋“ค์„ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•œ๋‹ค!

ํ•˜์ง€๋งŒ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ์•„์ฃผ์•„์ฃผ ๋น„ํšจ์œจ์ ์ด๋ฉฐ, Unit Test์˜ ์›์น™๊ณผ๋„ ๋งž์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋ž˜์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ…Œ์ŠคํŠธํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋ชจ์‚ฌํ•œ Mock ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ํ•ด๋‹น Mock ๊ฐ์ฒด์—์„œ ํ…Œ์ŠคํŠธ ์ƒํ™ฉ์„ ๋งŒ๋“ค์–ด ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•ด์•ผ ํ•œ๋‹ค!!

Mocking & Mock

ย  โ€œ์šด์˜ ํ™˜๊ฒฝ ๋Œ€๋น„ ์ œ์•ฝ์ด ๋งŽ์€ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๋™ํ•˜๊ฑฐ๋‚˜ ์‹ค์ œ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ๊ฐ€๋ น ๊ฐ€๋Šฅํ•˜๋”๋ผ๋„, ์ด๋ ‡๊ฒŒ ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์˜์กดํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” ํ•ด๋‹น ์„œ๋น„์Šค์— ๋ฌธ์ œ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ๊นจ์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์‹คํ–‰ ์†๋„๋„ ๋Š๋ฆด ์ˆ˜ ๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค.

ย  ๋”ฐ๋ผ์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ์™ธ๋ถ€์— ์˜์กดํ•˜๋Š” ๋ถ€๋ถ„์„ ์ž„์˜์˜ ๊ฐ€์งœ(Mock)๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ธฐ๋ฒ•์ด ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š”๋ฐ ์ด๋ฅผ ๋ชจํ‚น(Mocking)์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•ด, ๋ชจํ‚น(Mocking)์€ ์™ธ๋ถ€ ์„œ๋น„์Šค์— ์˜์กดํ•˜์ง€ ์•Š๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉ๋˜๋Š” ํ…Œ์ŠคํŒ… ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค.โ€ - article from here


ย  โ€œ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜๋Š” ์ฝ”๋“œ์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฌธ์ œ์ ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ…Œ๋ฒ ์ด์Šค ์ ‘์†๊ณผ ๊ฐ™์ด Network์ด๋‚˜ I/O ์ž‘์—…์ด ํฌํ•จ๋œ ํ…Œ์ŠคํŠธ๋Š” ์‹คํ–‰ ์†๋„๊ฐ€ ํ˜„์ €ํžˆ ๋–จ์–ด์งˆ ์ˆ˜ ๋ฐ–์— ์—†์Šต๋‹ˆ๋‹ค.
  • ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ๊ฐ€ ์ผœ์ ธ์„œ ํ•œ ๋ฒˆ์— ์‹คํ–‰ํ•ด์•ผ ํ•  ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ๋งŽ์ด์ง€๋ฉด ์ด๋Ÿฌํ•œ ์ž‘์€ ์†๋„ ์ €ํ•˜๋“ค์ด ๋ชจ์—ฌ ํฐ ์ด์Šˆ๊ฐ€ ๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, CI/CD ํŒŒ์ดํ”„๋ผ์ธ์˜ ์ผ๋ถ€๋กœ ํ…Œ์ŠคํŠธ๊ฐ€ ์ž๋™ํ™”๋˜์–ด ์ž์ฃผ ์‹คํ–‰๋˜์•ผ ํ•œ๋‹ค๋ฉด ๋” ํฐ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ…Œ์ŠคํŠธ ์ž์ฒด๋ฅผ ์œ„ํ•œ ์ฝ”๋“œ๋ณด๋‹ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐ์„ ๋งบ๊ณ  ํŠธ๋žœ์žญ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ฟผ๋ฆฌ๋ฅผ ์ „์†กํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋” ๊ธธ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋” ์ปค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ˆœ๊ฐ„ ์ผ์‹œ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์˜คํ”„๋ผ์ธ ์ž‘์—… ์ค‘์ด์—ˆ๋‹ค๋ฉด ํ•ด๋‹น ํ…Œ์ŠคํŠธ๋Š” ์‹คํŒจํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ…Œ์ŠคํŠธ๊ฐ€ ์ธํ”„๋ผ ํ™˜๊ฒฝ์— ์˜ํ–ฅ์„ ๋ฐ›๊ฒŒ๋ฉ๋‹ˆ๋‹ค. (non-deterministic)
  • ํ…Œ์ŠคํŠธ๊ฐ€ ์ข…๋ฃŒ ์ง ํ›„, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ณ€๊ฒฝ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์›๋ณตํ•˜๊ฑฐ๋‚˜ ํŠธ๋ Œ์žญ์…˜์„ rollback ํ•ด์ค˜์•ผ ํ•˜๋Š”๋ฐ ์ƒ๋‹นํžˆ ๋ฒˆ๊ฑฐ๋กœ์šด ์ž‘์—…์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌด์—‡๋ณด๋‹ค ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜๋ฉด ํŠน์ • ๊ธฐ๋Šฅ๋งŒ ๋ถ„๋ฆฌํ•ด์„œ ํ…Œ์ŠคํŠธํ•˜๊ฒ ๋‹ค๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test)์˜ ๊ทผ๋ณธ์ ์ธ ์‚ฌ์ƒ์— ๋ถ€ํ•ฉํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.โ€ - article from here

๊ทธ๋ž˜์„œ ์„œ๋ฒ„์˜ ์œ ๋‹› ํ…Œ์ŠคํŠธ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ง์ ‘ ์กฐ์ž‘ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์„œ๋ฒ„๋ฅผ ํ‰๋‚ด๋‚ด๋Š” MockRepository๋ฅผ ๋งŒ๋“ค์–ด ์ง„ํ–‰ํ•œ๋‹ค.


๋ณธ๋ž˜ service์—์„  Repository ๋ณ€์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ํ•ด๋‹น Repository๋ฅผ ์ด์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•œ๋‹ค. ํ…Œ์ŠคํŠธ์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด Repository๋ฅผ ๋ชจ์‚ฌํ•œ โ€œMockRepositoryโ€œ๋ฅผ ๋งŒ๋“ ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด UserRepository๋ฅผ ๋ชจ์‚ฌํ•œ MockRepository๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

class MockRepository {
  async findOneOrFail(query) {
    const user: User = new User();
    user.uuid = query.uuid;
    return user;
  }
}

describe('User', () => {
  let userService: UserService;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(User),
          useClass: MockRepository,
        },
      ],
    }).compile();
    userService = module.get<UserService>(UserService);
  });

  it('should', async () => {
    const userId = '42';
    const result = await userService.findUserById(userId);
    expect(result.uuid).toBe(userId);
  });
});

Repository๋ฅผ ๋ชจ์‚ฌํ•œ MockRepository๋ฅผ ์šด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Repository์— ์ •์˜๋œ ํ•จ์ˆ˜๋“ค์„ ์„ ์–ธํ•˜๊ณ  ๋ชจ์‚ฌํ•ด์ค˜์•ผ ํ•œ๋‹ค. ๋งŒ์•ฝ ํ…Œ์ŠคํŠธ ํ•˜๋ ค๋Š” service์˜ ํŠน์ • ํ•จ์ˆ˜, ์˜ˆ๋ฅผ ๋“ค๋ฉด findUserById ๊ฐ™์€ ํ•จ์ˆ˜๊ฐ€ ๋‚ด๋ถ€์—์„œ Repository์˜ repository.findOne()๊ณผ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, MockRepository์—์„œ ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๋ง์ด๋‹ค!



Jest: spyOn

์ฐธ๊ณ ์ž๋ฃŒ: link

1. jest.fn(); Mock function

jest.fn()์„ ์ด์šฉํ•ด Mock function์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

const mockFn = jest.fn();

Mock function์€ ์ผ๋ฐ˜ ํ•จ์ˆ˜์™€๋Š” ๋‹ฌ๋ฆฌ ๋ชจํ‚น์„ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ์— ํŠนํ™”ํ•œ ํ•จ์ˆ˜๋ฅผ ๋ชจ์‚ฌํ•œ โ€˜๊ฐ์ฒดโ€™์ž…๋‹ˆ๋‹ค.

* ์ธ์ž ์ž…๋ ฅ

mockFn()
mockFn(1)
mockFn("Lorem")
mockFn({ name: "Lorem", id: "Ipsum" })

* ๋ฆฌํ„ด ๊ฐ’ ์„ค์ •

mockFn.mockReturnValue("Lorem Ipsum");
console.log(mockFn()); // "Lorem Ipsum"

* Mock ๋น„๋™๊ธฐ ํ•จ์ˆ˜

mockFn.mockResolvedValue("Async resolve value");
mockFn.then((result) => {
  console.log(result); // "Async resolved value"
})

* Mock function ๊ตฌํ˜„

mockFn.mockImplementation((name) => `I am ${name}!`);
console.log(mockFn("Cantor")); // "I am Cantor!"

Mock function์˜ ์œ ์šฉ์„ฑ์€ Mock function์€ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค!!

mockFn("a")
mockFn(["b", "c"])

expect(mockFn).toBeCalledTimes(2)
expect(mockFn).toBeCalledWith("a")
expect(mockFn).toBeCalledWith(["b", "c"])


2. jest.spyOn(); Spy Function

์ง€๊ธˆ๊นŒ์ง€๋Š” ๋ชจ๋‘ ๊ธฐ์กด ๊ฐ์ฒด์„ ๋Œ€์‹ ํ•˜๋Š” โ€˜๋Œ€์—ญ(ไปฃๅฝน)โ€™์ธ Mock์„ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ๋ฅผ ์‚ดํŽด๋ดค๋‹ค. ํ•˜์ง€๋งŒ ๋ช‡๋ช‡ ๊ฒฝ์šฐ์—๋Š” ๊ธฐ์กด ๊ฐ์ฒด๋ฅผ Mock๋กœ ๋Œ€์ฒดํ•˜์ง€ ์–ด๋ ค์šธ ์ˆ˜๋„ ์žˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ โ€œSpy Functionโ€œ์ด๋‹ค!

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์ด calculator์— ์ •์˜๋œ add์˜ Spy Function์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

const calculator = {
  add: (a, b) => a + b,
}

const spyFn = jest.spyOn(calculator, "add")

const result = calculator.add(2, 3)

expect(spyFn).toBeCalledTimes(1)
expect(spyFn).toBeCalledWith(2, 3)
expect(result).toBe(5)

์•ž์—์„œ Spy function์€ Mock ํ•  ์ˆ˜ ์—†์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ํ–ˆ๋‹ค. Mock ํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ๋Š”, โ€œํ…Œ์ŠคํŒ… ๋Œ€์ƒ ํ•จ์ˆ˜ A๊ฐ€ ๋‹ค๋ฅธ ํ•จ์ˆ˜ B ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœ๋˜๋ฉฐ ์‚ฌ์šฉ๋˜๋Š” ์ƒํ™ฉ์ด๋ผ ํ•จ์ˆ˜ A๋ฅผ mockingํ•  ๊ฒฝ์šฐ, ํ•จ์ˆ˜ B๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์›๋ณธ์€ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  Spy ํ•œ๋‹ค.โ€๋ผ๊ณ  ํ•œ๋‹ค.

> Mock vs. Spy link

์ด๊ณณ์— ๊ฒŒ์‹œ๋œ .spyon() ํ•จ์ˆ˜์˜ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด์ž.

describe('UserService', () => {
  describe('์œ ์ € ์ •๋ณด ์ˆ˜์ •', () => {
    it('์กด์žฌํ•˜์ง€ ์•Š๋Š” ์œ ์ € ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ๊ฒฝ์šฐ BadRequestError ๋ฐœ์ƒํ•œ๋‹ค.', async () => {
      const userId = faker.random.uuid();

      const updateUserDto: UpdateUserDto = {
        firstName: faker.lorem.sentence(),
        lastName: faker.lorem.sentence(),
        isActive: false,
      };

      const userRepositoryFindOneSpy = jest
        .spyOn(userRepository, 'findOne')
        .mockResolvedValue(null);

      try {
        await userService.updateUser(userId, updateUserDto);
      } catch (e) {
        expect(e).toBeInstanceOf(BadRequestException);
        expect(e.message).toBe(Message.NOT_FOUND_USER_ITEM);
      }

      expect(userRepositoryFindOneSpy).toHaveBeenCalledWith({
        where: {
          id: userId,
        },
      });
    });
  });
})

์—ฌ๊ธฐ์„œ๋„ .spyOn()์œผ๋กœ repository์˜ ํ•จ์ˆ˜๋ฅผ ๋ชจ์‚ฌํ•˜๋”๋ผ๋„ ํ•จ์ˆ˜์˜ ๋กœ์ง์„ ์ˆ˜์ •ํ•˜๋Š” ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค!!

๋˜ํ•œ .spyOn()์˜ ๋ฆฌํ„ด์œผ๋กœ ์–ป์€ Spy Function ๊ฐ์ฒด๋Š” .toHaveBeenCalled...() ํ•จ์ˆ˜ ๋“ฑ์œผ๋กœ ํ…Œ์ŠคํŒ…ํ•  ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๋ชจ์‚ฌํ•œ ํ•จ์ˆ˜๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ–ˆ๋Š”์ง€๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๋ฐ์— ์‚ฌ์šฉ๋œ๋‹ค. ์šฐ๋ฆฌ๊ฐ€ ๊ฒ€์ฆํ•  ๋Œ€์ƒ์€ .spyOn()์œผ๋กœ Mockingํ•œ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค. .spyOn()์„ ํฌํ•จํ•œ Mocking์€ ๋‹จ์ง€ ์˜์กด์„ฑ์„ ๋Š๊ธฐ ์œ„ํ•œ ์ˆ˜๋‹จ์ผ ๋ฟ์ด๋‹ค!!

๊ฒฐ๊ตญ .spyOn()์„ ์‚ฌ์šฉํ•˜๋”๋ผ๋„ ๊ฒฐ๊ตญ์—” ๊ธฐ์กด Mocking๊ณผ ๋น„์Šทํ•œ ๋งฅ๋ฝ์œผ๋กœ ํ…Œ์ŠคํŒ…์ด ์ง„ํ–‰๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค! ์กฐ์‚ผ๋ชจ์‚ฌ

Reference

Categories:

Updated: