TDD with Jest in NestJS

임성준·2022년 6월 23일
0
post-thumbnail

TDD (Test-driven development)

TDD은 소프트웨어 개발 방법론 중의 하나로, 선 개발 후 테스트 방식이 아닌 선 테스트 후 개발 방식의 프로그래밍 방법을 말한다. 다시 말해 먼저 자동화된 테스트 코드를 작성한 후 테스트를 통과하기 위한 코드를 개발하는 방식의 개발 방식을 말한다.


WHY❓ TDD❗️

  • 객체 지향적인 코드 개발
    TDD는 코드의 재사용 보장을 명시하므로 TDD를 통한 소프트웨어 개발 시 기능별로 모듈화가 이루어진다. 이는 의존성과 종속성이 낮은 모듈로 조합된 소프트웨어 개발을 가능하게 하며, 필요에 따라 모듈을 추가하거나 제거해도 소프트웨어 전체 구조에 영향을 미치지 않게 된다.

  • 설계 수정시간의 단축
    테스트코드를 먼저 작성하기 때문에 최초 설계안을 만족하게 하며 입출력 구조와 기능의 정의를 명확하게 하게 되므로 설계의 구조적 문제를 바로 찾아낼 수 있다.

  • 유지보수(리팩토링)의 용이성
    기본적으로 단위 테스트 기반의 테스트 코드를 작성하기 때문에 추후 문제가 발생하였을 때 각각의 모듈별로 테스트를 진행해보면 문제의 지점을 쉽게 찾을 수 있다.

  • 테스트 문서의 대체 가능
    대부분의 개발 프로젝트에서 테스트를 진행하는 경우 단순 통합 테스트에 지나지 않는다. TDD를 하게 될 때 테스팅을 자동화시킴과 동시에 더욱 정확한 테스트 근거를 산출해 정의서를 작성할 수 있다.

🚀   Jest를이용한 TDD 구성

✅   파일 생성 :   이름.spect.ts
✅   테스트 명령어 :   yarn test

1. 한개 테스트하기

it('더하기 테스트', () => {
  const a = 1;
  const b = 2;

  expect(a + b).toBe(3);
});

2. 여러개 묶음으로 테스트하기

describe('나의 테스트 그룹', () => {
  it('더하기 테스트', () => {
    const a = 1;
    const b = 2;

    expect(a + b).toBe(3);
  });

  it('곱하기 테스트', () => {
    const a = 1;
    const b = 2;

    expect(a * b).toBe(2);
  });
});

3. 상품 구매하기 테스트 예제

describe('상품 구매 테스트', () => {
  // beforeEach : 각각을 테스트하기 전 실행
  // ex) Login
  beforeEach(() => {});

  it('돈 검증하기', () => {
    // 돈이 충분하다고 가정
    const result = true;
    expect(result).toBe(true);
  });

  it('상품 구매하기', () => {
    // 상품 구매 성공 가정
    const result = true;
    expect(result).toBe(true);
  });

4. user service 테스트 코드 예제

import { ConflictException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';
import { UserService } from '../user.service';

ype MockRepository<T = any> = Partial<Record<keyof Repository<T>, jest.Mock>>;

describe('UserService', () => {
  // userService, userRepository 타입 설정
  let userService: UserService;
  let userRepository: MockRepository<User>;

  beforeEach(async () => {
    const userModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: getRepositoryToken(User),
          useClass: MockUserRepository,
        },
      ],
    }).compile();

    userService = userModule.get<UserService>(UserService);
    userRepository = userModule.get<MockRepository<User>>(
      getRepositoryToken(User),
    );
  });

  describe('create', () => {
    it('이메일 존재여부 확인', async () => {
      const userRepositorySpyFindOne = jest.spyOn(userRepository, 'findOne');
      const userRepositorySpySave = jest.spyOn(userRepository, 'save');

      const myData = {
        email: 'a@a.com',
        hashedPwd: '1234',
        name: '철수',
        age: 13,
      };

      try {
        await userService.create({ ...myData });
      } catch (error) {
        expect(error).toBeInstanceOf(ConflictException);
      }

      expect(userRepositorySpyFindOne).toBeCalledTimes(1);
      expect(userRepositorySpySave).toBeCalledTimes(0);
    });

    it('회원 등록 잘 됐는지 검증!!', async () => {
      const userRepositorySpyFindOne = jest.spyOn(userRepository, 'findOne');
      const userRepositorySpySave = jest.spyOn(userRepository, 'save');

      const myData = {
        email: 'bbb@bbb.com',
        hashedPwd: '1234',
        name: '철수',
        age: 13,
      };

      const myResultData = {
        email: 'bbb@bbb.com',
        password: '1234',
        name: '철수',
        age: 13,
      };

      const result = await userService.create({ ...myData });
      // 객체 or 배열을 확인하기 위해서 toStrictEqual을 사용
      // 객체와 배열은 내용이 같아보여도 같지 않다.
      expect(result).toStrictEqual(myResultData);
      expect(userRepositorySpyFindOne).toBeCalledTimes(1);
      expect(userRepositorySpySave).toBeCalledTimes(1);
    });
  });

  describe('findOne', () => {});
});

class MockUserRepository {
  mydb = [
    {
      email: 'a@a.com',
      hashedPwd: '0000',
      name: '짱구',
      age: 5,
    },
  ];
  findOne({ email }) {
    const users = this.mydb.filter((el) => {
      el.email === email;
    });

    if (users.length) return users[0];
    return null;
  }

  save({ email, password, name, age }) {
    this.mydb.push({
      email,
      hashedPwd: password,
      name,
      age,
    });

    return { email, password, name, age };
  }
}

참조

profile
오늘도 공부 📖🌙

0개의 댓글