ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋ฐ˜์‘ํ˜•

๐Ÿ“Œ NestJS ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ: ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์‹ค์ „๊นŒ์ง€ - NestJS์—์„œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๊ณผ TDD ์‹ค์ฒœ

์„œ๋น„์Šค์˜ ์•ˆ์ •์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ์„ ๋ณด์žฅํ•˜๋ ค๋ฉด ํ…Œ์ŠคํŠธ๋Š” ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.
NestJS๋Š” Jest ๊ธฐ๋ฐ˜์˜ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋ฉฐ, ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(TDD)์„ ์‰ฝ๊ฒŒ ์‹คํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” ์œ ๋‹› ํ…Œ์ŠคํŠธ(Unit Test), ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ(E2E Test), Mocking ์ „๋žต, TDD ํ๋ฆ„ ์˜ˆ์ œ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ๐Ÿš€


12.1 NestJS์—์„œ์˜ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ

NestJS๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด ์ž๋™์œผ๋กœ Jest ํ™˜๊ฒฝ์ด ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

โœ… ๊ธฐ๋ณธ ํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ ํŒŒ์ผ๋“ค

  • jest.config.ts: Jest ์„ค์ • ํŒŒ์ผ
  • test/app.e2e-spec.ts: ๊ธฐ๋ณธ End-to-End ํ…Œ์ŠคํŠธ
  • ๊ฐ ๋ชจ๋“ˆ๋งˆ๋‹ค *.spec.ts ํ˜•ํƒœ๋กœ ์œ ๋‹› ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๊ฐ€๋Šฅ

๐Ÿ’ก NestJS CLI๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ƒ์„ฑ์„ ์ž๋™ํ™”ํ•ด์ค๋‹ˆ๋‹ค.


12.2 ์œ ๋‹› ํ…Œ์ŠคํŠธ (Unit Test)

์œ ๋‹› ํ…Œ์ŠคํŠธ๋Š” ์„œ๋น„์Šค๋‚˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ์ž‘์€ ๋‹จ์œ„์˜ ๋กœ์ง์„ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ์ž…๋‹ˆ๋‹ค.

โœ… ์„œ๋น„์Šค ์œ ๋‹› ํ…Œ์ŠคํŠธ ์˜ˆ์‹œ

๐Ÿ“‚ users.service.ts

@Injectable()
export class UsersService {
  private users = [{ id: 1, name: 'Alice' }];

  findAll() {
    return this.users;
  }

  findOne(id: number) {
    return this.users.find(user => user.id === id);
  }
}

๐Ÿ“‚ users.service.spec.ts

import { UsersService } from './users.service';

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

  beforeEach(() => {
    service = new UsersService();
  });

  it('should return all users', () => {
    expect(service.findAll()).toHaveLength(1);
  });

  it('should return a user by ID', () => {
    expect(service.findOne(1)).toEqual({ id: 1, name: 'Alice' });
  });
});

โœ… beforeEach()๋ฅผ ํ†ตํ•ด ๋งค ํ…Œ์ŠคํŠธ๋งˆ๋‹ค fresh ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
โœ… expect()๋กœ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๊ฒ€์ฆ


12.3 ์˜์กด์„ฑ ์ฃผ์ž…์ด ์žˆ๋Š” ์„œ๋น„์Šค ํ…Œ์ŠคํŠธ

๋ฐ˜์‘ํ˜•

NestJS๋Š” DI ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ํ…Œ์ŠคํŠธ์—์„œ๋„ TestingModule์„ ํ†ตํ•ด ์˜์กด์„ฑ์„ ์ฃผ์ž…ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';

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

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

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

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

๐Ÿ’ก NestJS์˜ Test.createTestingModule์„ ํ™œ์šฉํ•˜๋ฉด ์‹ค์ œ ์„œ๋น„์Šค์ฒ˜๋Ÿผ ํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ ๊ฐ€๋Šฅ


12.4 ์ปจํŠธ๋กค๋Ÿฌ ํ…Œ์ŠคํŠธ (Mocking ํฌํ•จ)

๐Ÿ“‚ users.controller.ts

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }
}

๐Ÿ“‚ users.controller.spec.ts

describe('UsersController', () => {
  let controller: UsersController;
  let service: UsersService;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      controllers: [UsersController],
      providers: [
        {
          provide: UsersService,
          useValue: {
            findAll: jest.fn().mockReturnValue([{ id: 1, name: 'MockUser' }]),
          },
        },
      ],
    }).compile();

    controller = module.get<UsersController>(UsersController);
    service = module.get<UsersService>(UsersService);
  });

  it('should return mocked users', () => {
    expect(controller.findAll()).toEqual([{ id: 1, name: 'MockUser' }]);
  });
});

โœ… ์‹ค์ œ ์„œ๋น„์Šค ๋Œ€์‹  mock ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…
โœ… jest.fn()์œผ๋กœ ๋ฉ”์„œ๋“œ ๋ชจํ‚น ๋ฐ ๋ฐ˜ํ™˜ ๊ฐ’ ์ง€์ •


12.5 E2E ํ…Œ์ŠคํŠธ (End-to-End)

E2E ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ์ „์ฒด ํ๋ฆ„์„ ํ†ตํ•ฉ์ ์œผ๋กœ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‚ test/app.e2e-spec.ts

import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('AppController (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleFixture = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

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

  it('/ (GET)', () => {
    return request(app.getHttpServer())
      .get('/')
      .expect(200)
      .expect('Hello World!');
  });
});

โœ… supertest๋ฅผ ํ†ตํ•ด ์‹ค์ œ HTTP ์š”์ฒญ์„ ๋ณด๋‚ด ํ…Œ์ŠคํŠธ
โœ… app.getHttpServer()๋กœ NestJS ์ธ์Šคํ„ด์Šค ๊ธฐ๋ฐ˜ ์„œ๋ฒ„ ์ ‘๊ทผ


12.6 TDD(Test Driven Development) ํ๋ฆ„

TDD๋Š” ๋‹ค์Œ์˜ ์ˆœ์„œ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค:

  1. Fail: ๋จผ์ € ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑ
  2. Pass: ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋„๋ก ์ตœ์†Œํ•œ์˜ ์ฝ”๋“œ ์ž‘์„ฑ
  3. Refactor: ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผ๋œ ์ƒํƒœ์—์„œ ๋ฆฌํŒฉํ† ๋ง

๐Ÿ’ก ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜๋ฉด ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž์ถ˜ ์•ˆ์ •์ ์ธ ์ฝ”๋“œ ๊ตฌํ˜„ ๊ฐ€๋Šฅ


12.7 ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๋ฐ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ

โœ… ํ…Œ์ŠคํŠธ ์‹คํ–‰

npm run test

โœ… ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ

npm run test:cov

์ถœ๋ ฅ ์˜ˆ์‹œ:

File                   | % Stmts | % Branch | % Funcs | % Lines |
-----------------------|---------|----------|---------|---------|
src/users/users.service.ts |   100% |      100% |   100% |   100% |

๐Ÿ’ก ์ปค๋ฒ„๋ฆฌ์ง€ ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ ๋ฒ”์œ„๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ™•์ธ ๊ฐ€๋Šฅ


12.8 ๊ฒฐ๋ก : NestJS ํ…Œ์ŠคํŠธ์™€ TDD๋กœ ์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ์ฝ”๋“œ ๋งŒ๋“ค๊ธฐ

โœ… NestJS๋Š” Jest ๊ธฐ๋ฐ˜์˜ ์œ ๋‹› & ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์„ ๊ธฐ๋ณธ ์ œ๊ณต
โœ… TestingModule๋กœ DI ์ฃผ์ž… ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ
โœ… ์„œ๋น„์Šค, ์ปจํŠธ๋กค๋Ÿฌ, E2E ํ…Œ์ŠคํŠธ๊นŒ์ง€ ์™„์ „ํ•œ ํ…Œ์ŠคํŠธ ์ฒด๊ณ„ ๊ตฌ์ถ• ๊ฐ€๋Šฅ
โœ… TDD ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœ ์‹œ ์•ˆ์ •์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋Œ€ํญ ํ–ฅ์ƒ๋จ

๋‹ค์Œ ๊ธ€์—์„œ๋Š” NestJS ํ”„๋กœ์ ํŠธ์˜ ๊ตฌ์กฐ ์ •๋ฆฌ์™€ ๋ชจ๋…ธ๋ ˆํฌ(Monorepo) ํŒจํ„ด ์ ์šฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค! ๐Ÿš€


๐Ÿ” ๋‹ค์Œ ๊ธ€ ์˜ˆ๊ณ : NestJS ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์ •๋ฆฌ ๋ฐ ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์„ฑ ์ „๋žต

๐Ÿ“Œ ๋‹ค์Œ ํŽธ: 13. NestJS ๋ชจ๋…ธ๋ ˆํฌ ๊ตฌ์กฐ์™€ ์‹ค์ „ ์ ์šฉ๋ฒ•

 

โ€ป ์ด ํฌ์ŠคํŒ…์€ ์ฟ ํŒก ํŒŒํŠธ๋„ˆ์Šค ํ™œ๋™์˜ ์ผํ™˜์œผ๋กœ, ์ด์— ๋”ฐ๋ฅธ ์ผ์ •์•ก์˜ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ œ๊ณต๋ฐ›์Šต๋‹ˆ๋‹ค.
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
ยซ   2025/04   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
๊ธ€ ๋ณด๊ด€ํ•จ
๋ฐ˜์‘ํ˜•