Skip to content

NestJS에서 단위 테스트를 해보자!

Dohyeon Han edited this page Dec 5, 2022 · 1 revision

Unit Test?

  • 우리가 짠 코드가 원하는 대로 돌아가는지 어떻게 확인 할 수 있을까요? 가장 쉽게 떠올릴 수 있는 방법은 직접 실행 시켜서 확인해보는 것입니다. 하지만 새로운 기능이 생기고 리팩토링이 될 때마다 같은 테스트를 해 볼 수는 없는 노릇입니다. 그럴 때 사용하는 것이 테스트 코드이고 여러 테스트 방법 중 유닛 테스트는 함수 단위로 테스트를 실행하는 것입니다.

Mock

@Injectable()
export class UserService {
  constructor(
    private readonly userRepository: UserRepository
  ) {}
  ...
  // 유저 전체 조회
  async findAll(): Promise<User[]> {
    return await this.userRepository.findAll();
  }
  ...
}
  • 현재 프로젝트 코드의 일부입니다.
  • findAll() 함수는 userRepository라는 인스턴스에 의존하고 있습니다.
  • 함수 단위로 테스트를 하기 위해서는 이런 의존성을 끊어야 합니다.
  • 이럴 때 사용할 수 있는 것이 바로 mocking입니다. 즉 가짜 userRepository를 주입해줘서 사용한다는 뜻입니다.

Spy

  • 어떤 함수의 동작을 감시할 때 사용합니다.
  • mocking된 함수, 진짜 함수 모두에 spy를 적용할 수 있습니다.
  • spy를 통해 인자값, 반환값 등을 테스트할 수 있습니다.

적용해보기

nest g res user라는 명령어를 실행하면 아래와 같은 user.service.spec.ts라는 파일이 생성됩니다.

import { Test, TestingModule } from '@nestjs/testing';

import { UserService } from './user.service';

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

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

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

  it('should be defined', () => {
    expect(service).toBeDefined();
  });
});
  • 테스트를 시 절대 경로를 읽을 수 없는 오류가 있을 수 있기 때문에 package.json의 "jest"에 해당 코드를 추가해줍니다.
"jest": {
  "moduleNameMapper": {
    "^src/(.*)$": "<rootDir>/$1"
  },
}
  • UserRepository를 주입 받아 findAll()을 작성한 후 해당 테스트를 실행해보면 실패가 발생합니다. 그 이유는 UserService는 UserRepository를 주입 받아 사용하고 있는데, 주입을 해주지 않았기 때문입니다.
  • 테스트 방식 중에 mock과 spy가 있는데, 두 가지를 적용해보겠습니다.

mock

  • 먼저 주입할 repository 객체를 만듭니다.
  • 이런 가짜 객체를 mock 객체라고 합니다.
const mockUserRepository = {
  findAll: jest.fn(),
};
  • 여기서 jest.fn은 가짜 함수를 생성하기 위한 것이고, 이것을 통해 임의로 반환 값을 정할 수 있습니다.
  • providers에 주입 받을 타입과 실제로 주입할 객체를 명시해줍니다.
providers: [
  UserService,
  {
    provide: UserRepository, // 주입할 타입
    useValue: mockUserRepository, // 실제로 주입할 객체
  },
],
  • 이제 테스트 코드를 작성해 봅시다.
  it('mock repository로 유저 전체 조회', () => {
    const userStub: User = {
      authProvider: 'google',
      authId: '12345',
      email: '[email protected]',
      username: 'abcde',
      _id: new Types.ObjectId('637f8ec80b9f9e762f47c269'),
      createdAt: '',
      updatedAt: '',
    };
    const users = [userStub]; 

    mockUserRepository.findAll.mockResolvedValue(users); // 비동기로 findAll의 반환값 지정

    service.findAll().then((result) => {
      expect(result).toEqual(users); // result의 결과가 users와 같으면 통과
    });
  });
  • 해당 테스트를 수행하면 통과하는 것을 볼 수 있습니다.

image

  • 전체 코드
import { Types } from 'mongoose';
import { Test, TestingModule } from '@nestjs/testing';

import { User } from './entities/user.entity';
import { UserService } from './user.service';
import { UserRepository } from './user.repository';

const mockUserRepository = {
  findAll: jest.fn(),
};

const userStub: User = {
  authProvider: 'google',
  authId: '12345',
  email: '[email protected]',
  username: 'abcde',
  _id: new Types.ObjectId('637f8ec80b9f9e762f47c269'),
  createdAt: '',
  updatedAt: '',
};

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

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: UserRepository,
          useValue: mockUserRepository,
        },
      ],
    }).compile();

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

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

  it('mock repository로 유저 전체 조회', () => {
    const users = [userStub];

    mockUserRepository.findAll.mockResolvedValue(users);

    service.findAll().then((result) => {
      expect(result).toEqual(users);
    });
  });
});

spy

  • 기존 코드에 spy를 추가해보겠습니다.
  • spy는 어떤 메소드를 감시하는 기능입니다. spy를 적용하여 해당 메소드가 얼마나 호출 되었는 지, 어떤 값이 전달되었는 지를 확인할 수 있습니다.
  it('spy를 적용한 mock repository', () => {
    const users = [userStub];

    const spy = jest.spyOn(service, 'findAll');

    mockUserRepository.findAll.mockResolvedValue(users); // 비동기로 전체 유저 조회
    service.findAll().then((result) => {
      expect(result).toEqual(users);
    });
  });
  • 이제 spy를 통해 findAll 함수를 감시할 수 있습니다.
  • spy의 값을 테스트해보겠습니다.
  it('spy를 적용한 mock repository', () => {
    const users = [userStub];

    const spy = jest.spyOn(service, 'findAll');

    mockUserRepository.findAll.mockResolvedValue(users); // 비동기로 전체 유저 조회
    service.findAll().then((result) => {
      expect(result).toEqual(users);
      expect(spy).toBeCalledTimes(1); // 함수는 1번 호출
      expect(spy).toBeCalledWith(); // 빈 값이 전달
    });
  • 해당 함수를 테스트하면 통과하는 것을 볼 수 있습니다.

얼리버드

프로젝트

개발일지

스프린트 계획

멘토링

데일리 스크럼

데일리 개인 회고

위클리 그룹 회고

스터디

Clone this wiki locally