signUp service code

@Transactional()
  async signUp(signUpRequestDto: SignUpRequestDto): Promise<any> {
    const { email, name, password } = signUpRequestDto;
    const user = await this.userRepository.findOneUserByEmailLockMode(email);
    if (user) {
      throw new ConflictException('The email is already in use');
    }
    const saltRounds = this.configService.get<string>('SALT_ROUNDS');
    if (saltRounds === undefined) {
      throw new Error('SALT_ROUNDS is not defined in the configuration.');
    }
    const hashedPassword = await bcrypt.hash(password, parseInt(saltRounds));

    const newUserEntity = new User({ name, email, password: hashedPassword });
    const newUser = await this.userRepository.signUp(newUserEntity);
    return new SignUpResponseDto({ ...newUser });
  }

 

고려해야 할 것 들이 많아서 한참 고생을 했다.

다음에 참고하기 위해서 기록을 남김

 

목적

  • signUp 안에 구현된 코드들이 원하는대로 작동하는 가만 확인하기

테스트시 확인해야 하는 사항들

  • configService.get
    • 'SALT_ROUNDS' 를 정상적으로 받는가?
    • undefined 이면 에러를 throw 하는가?
  • bcrypt
    • password, parseInt(saltRounds) 변수를 받아서 사용하고 있는가?
    • hashedPassword 를 잘 반환하는가?
  • userRepository
    • 중복되는 email 이 있을시 에러를 throw 하는가?

 

bcrypt mocking

import * as bcrypt from 'bcrypt';

// import 한 함수를 중 사용할 method 를 테스트하기위해서 mocking
jest.mock('bcrypt', () => ({
  hash: jest.fn(),
}));

// 값을 반환 받기 위한 코드
jest.spyOn(bcrypt, 'hash').mockImplementation(async () => 'hashedpassword');

 

transactional 을 사용하고 있기 때문에 이 부분을 mocking

jest.mock('typeorm-transactional', () => ({
  Transactional: () => jest.fn(),
  initializeTransactionalContext: jest.fn(),
}));

 

 .env 파일을 읽어오는 configureService 를 mocking

const mockConfigService = {
  get: jest.fn(),
};

// 원하는 값이 없는 경우
configService.get.mockReturnValue(undefined);

// 원하는 값이 있는 경우
const saltRounds = '10';
configService.get.mockReturnValue(saltRounds);

 

 

 

 

완성된 코드

import { UserService } from '../application/user.service';
import { UserRepository } from '../domain/user.repository';
import { Test, TestingModule } from '@nestjs/testing';
import { USER_REPOSITORY } from '../../common/const/inject.constant';
import { SignUpRequestDto } from '../dto/signUp.request.dto';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../../auth/application/auth.service';
import { User } from '../domain/User.entity';
import * as bcrypt from 'bcrypt';
import { ConflictException } from '@nestjs/common';

jest.mock('typeorm-transactional', () => ({
  Transactional: () => jest.fn(),
  initializeTransactionalContext: jest.fn(),
}));

jest.mock('bcrypt', () => ({
  hash: jest.fn(),
}));

const mockUserRepository: jest.Mocked<UserRepository> = {
  signUp: jest.fn(),
  findOneUserByEmailLockMode: jest.fn(),
  findOneUserByEmail: jest.fn(),
  findOneUserById: jest.fn(),
  softDeleteUser: jest.fn(),
};

const mockConfigService = {
  get: jest.fn(),
};

describe('UserRepository', () => {
  let userRepository: jest.Mocked<typeof mockUserRepository>;
  let userService: UserService;
  let configService: jest.Mocked<typeof mockConfigService>;

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

    userService = module.get<UserService>(UserService);
    configService = module.get(ConfigService);
    userRepository = module.get(USER_REPOSITORY);
  });

  describe('signUp', () => {
    it('should throw ConflictException if email is already in use', async () => {
      const usedEmail = 'useremail@email.com';
      const signUpRequestDto: SignUpRequestDto = { name: 'tester', email: usedEmail, password: '12345678' };
      const { email, name, password } = signUpRequestDto;
      const user: User = new User({ email, name, password });
      user.id = 1;

      userRepository.findOneUserByEmailLockMode.mockResolvedValue(user);

      await expect(userService.signUp(signUpRequestDto)).rejects.toThrow(ConflictException);
    });

    it('should throw Error if saltRounds is not set in configService', async () => {
      const usedEmail = 'useremail@email.com';
      const signUpRequestDto: SignUpRequestDto = { name: 'tester', email: usedEmail, password: '12345678' };
      const { email, name, password } = signUpRequestDto;
      const user: User = new User({ email, name, password });
      user.id = 1;

      userRepository.findOneUserByEmailLockMode.mockResolvedValue(null);
      configService.get.mockReturnValue(undefined);

      await expect(userService.signUp(signUpRequestDto)).rejects.toThrow(Error);
    });

    it('should sign up a new user ', async () => {
      const signUpRequestDto: SignUpRequestDto = { name: 'tester', email: 'test@email.com', password: '12345678' };
      const { email, name, password } = signUpRequestDto;
      const newUser: User = new User({ email, name, password });
      newUser.id = 1;
      const saltRounds = '10';

      userRepository.findOneUserByEmailLockMode.mockResolvedValue(null);
      configService.get.mockReturnValue(saltRounds);
      jest.spyOn(bcrypt, 'hash').mockImplementation(async () => 'hashedpassword');
      userRepository.signUp.mockResolvedValue(newUser);

      const result = await userService.signUp(signUpRequestDto);
      console.log(result);
      expect(bcrypt.hash).toHaveBeenCalledWith(password, parseInt(saltRounds));
    });
  });
});

+ Recent posts