Daily Logs/TIL (Today I Learned)

const로 선언된 변수는 재할당이 불가능하다.

Jcob.moon 2024. 12. 11. 15:02

프로젝트 마무리 단계에 코드 리펙토링 중 

import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { JwtService } from '@nestjs/jwt';
import { HashingService } from 'src/interface/hashing-interface';
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
import {
  mockHashingService,
  mockJwtService,
  mockUserRepository,
} from './__mocks__/mock.auth.service';
import {
  loginDTO,
  mockPayload,
  mockRequest,
  mockUser,
  signUpDto,
  wrongPassword,
} from './__mocks__/mock.auth.data';
import { IUserRepository } from 'src/interface/IUserRepository';

describe('AuthService', () => {
  let authService: AuthService;
  let jwtService: JwtService;
  let hashingService: HashingService;
  let userRepository: IUserRepository;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AuthService,
        {
          provide: 'IUserRepository',
          useValue: mockUserRepository,
        },
        {
          provide: 'HashingService',
          useValue: mockHashingService,
        },
        {
          provide: JwtService,
          useValue: mockJwtService,
        },
      ],
    }).compile();

    authService = module.get<AuthService>(AuthService);
    jwtService = module.get<JwtService>(JwtService);
    hashingService = module.get<HashingService>('HashingService');
    userRepository = module.get<IUserRepository>('IUserRepository');

    
  });

  describe('회원가입', () => {
    it('회원가입 시 인증번호가 일치하지 않으면 BadRequestException 던짐', async () => {
      await expect(authService.signUp(signUpDto, mockRequest)).rejects.toThrow(BadRequestException);
    });

    it('회원가입 시 이메일이 중복되면 BadRequestException 던짐', async () => {
      mockRequest.session.code = '1234';
      mockUserRepository.findByEmail.mockResolvedValue({ email: signUpDto.email });
      mockUserRepository.findByNickname.mockResolvedValue(null);
      mockUserRepository.findByPhoneNumber.mockResolvedValue(null);

      await expect(authService.signUp(signUpDto, mockRequest)).rejects.toThrow(BadRequestException);

      expect(mockUserRepository.findByEmail).toHaveBeenCalledWith(signUpDto.email);
    });

    it('회원가입 시 비밀번호 확인이 다르면 BadRequestException 던짐', async () => {
      mockUserRepository.findByEmail.mockResolvedValue(null);

      await expect(authService.signUp(wrongPassword, mockRequest)).rejects.toThrow(
        BadRequestException,
      );
    });

    it('회원가입 시 닉네임이 중복되면 BadRequestException 던짐', async () => {
      mockUserRepository.findByNickname.mockResolvedValue({ nickname: 'nickname' });

      await expect(authService.signUp(signUpDto, mockRequest)).rejects.toThrow(BadRequestException);

      expect(mockUserRepository.findByNickname).toHaveBeenCalledWith(signUpDto.nickname);
    });

    it('회원가입 시 전화번호가 중복되면 BadRequestException 던짐', async () => {
      mockUserRepository.findByEmail.mockResolvedValue(null);
      mockUserRepository.findByNickname.mockResolvedValue(null);
      mockUserRepository.findByPhoneNumber.mockResolvedValue({ phoneNumber: '01000000000' });

      await expect(authService.signUp(signUpDto, mockRequest)).rejects.toThrow(BadRequestException);

      expect(mockUserRepository.findByPhoneNumber).toHaveBeenCalledWith(signUpDto.phoneNumber);
    });

    it('회원가입 성공 검증', async () => {
      mockUserRepository.findByEmail.mockResolvedValue(null);
      mockUserRepository.findByNickname.mockResolvedValue(null);
      mockUserRepository.findByPhoneNumber.mockResolvedValue(null);
      mockHashingService.hash.mockResolvedValue('hashedPassword');

      const result = await authService.signUp(signUpDto, mockRequest);

      expect(result).toEqual({
        message: '회원가입에 성공했습니다.',
      });
      expect(mockHashingService.hash).toHaveBeenCalledWith(signUpDto.password);
      expect(mockUserRepository.save).toHaveBeenCalledWith({
        email: signUpDto.email,
        password: 'hashedPassword',
        name: signUpDto.name,
        nickname: signUpDto.nickname,
        phoneNumber: signUpDto.phoneNumber,
      });
    });
  });

  describe('login', () => {
    it('유저가 존재하지 않으면 UnauthorizedException 던짐', async () => {
      mockUserRepository.findByEmail.mockResolvedValue(null);

      await expect(authService.logIn(loginDTO)).rejects.toThrow(UnauthorizedException);
    });

    it('비밀번호가 일치하지 않으면 UnauthorizedException 던짐', async () => {
      mockUserRepository.findByEmail.mockResolvedValue(mockUser);

      mockHashingService.compare.mockResolvedValue(false);

      await expect(authService.logIn(loginDTO)).rejects.toThrow(UnauthorizedException);
    });

    it('유저가 존재하고 비밀번호가 일치하면 엑세스 토큰을 던짐', async () => {
      mockUserRepository.findByEmail.mockResolvedValue(mockUser);

      mockHashingService.compare.mockResolvedValue(true);

      mockJwtService.sign.mockReturnValue(mockPayload);

      const result = await authService.logIn(loginDTO);

      expect(result).toEqual({
        access_token: mockPayload,
      });

      expect(mockUserRepository.findByEmail).toHaveBeenCalledWith(loginDTO.email);

      expect(hashingService.compare).toHaveBeenCalled();
    });
  });
});



__mocks__.moc.auth.data.ts

export const mockRequest: Request = {
  session: { code: 'wrongCode' },
} as unknown as Request;

 


이런식으로 모킹 데이터를 하나의 파일에 모아서 export 하는 방식으로 
테스트 코드를 리펙토링 하고 있였다.

 
  it('회원가입 시 인증번호가 일치하지 않으면 BadRequestException 던짐', async () => {
      await expect(authService.signUp(signUpDto, mockRequest)).rejects.toThrow(BadRequestException);
    });


인증 번호 불일치 시 에러메세지를 띄우고 그리고 이후는 인증번호가 일치해야하는 경우 이기때문에

export const signUpDto: SignUpDto = {
  email: 'test@test.com',
  password: 'password',
  confirmedPassword: 'password',
  name: 'name',
  nickname: 'nickname',
  phoneNumber: '01000000000',
  code: '1234',
};
mockRequest.session.code = '1234';


const 는 참조 값을 고정하고 객체 내부의 속성을 해당 참조값 내부에 저장되어 있으므로 변경이 가능하다는 점을 이용하여
이런식으로 mockRequest.session.code = '1234'; 로 그 이후의 테스트 코드에서 mockRequest 를 참조시
인증번호가 일치하는 상태로 변경하여


중복해서 작성해야하는 경우를 피했다.

요약 : 
1. const 원리 

const obj = { key: 'value' };
obj.key = 'newValue'; // 가능
console.log(obj); // { key: 'newValue' }

obj = {}; // 불가능: TypeError: Assignment to constant variable


2. 프로젝트에서 활용 

export const mockRequest: Request = {
  session: { code: 'wrongCode' },
} as unknown as Request;

// 테스트에서 속성 값 수정
mockRequest.session.code = '1234'; // 가능
it('테스트 1', () => {
  mockRequest.session.code = '1234'; // 수정
  console.log(mockRequest.session.code); // '1234'
});

it('테스트 2', () => {
  console.log(mockRequest.session.code); // 여전히 '1234'
});



굉장히 기본적인 이고 이제는 당연할 수 있는 부분이지만 기본적인 부분을 리마인드 하며 적어보았다.