본문 바로가기
개발/차근차근 개발일지 TIL

TIL 230515 클론코딩4 [TEST CODE]

by 코딩하는짱구 2023. 5. 18.
반응형

TIL 230515 클론코딩4 [TEST CODE]

✅오늘학습 Keyword

  • 회원가입/로그인 controller, service 의 unit test code 작성하기 

✅오늘 겪은 문제 및 해결

1. service-signup 파트에서 회원가입 정보를 재가공해서 출력시키는경우, test code의 방향성 

service unit test code 작성시 result와 expectedvalue가 일치하지 않는 오류가 지속적으로 발생했다. 그 이유는 signup service에서 내가 회원가입 정보를 return할때 원하는 양식으로 재가공하고 있기 때문이였다.

 

예를 들면, 나는 아래와 같이 signup에 성공한 회원의 data를 재가공해서 불러오고 있는데, 

  signup = async (email, name, password, birthday, gender, profile_url) => {
    const user = await this.userRepository.createUser(
      email,
      name,
      password,
      birthday,
      gender,
      profile_url
    );

    let userInfo = {
      user_id: user.user_id,
      email: user.email,
      name: user.name,
      password: user.password,
      birthday: user.birthday,
      gender: user.gender,
      profile_url: user.profile_url,
    };
    return userInfo;
  };

 

test에선 user 정보 그 자체를 반환하도록 코드를 짜놨기 때문에 함수값과 예상값의 불일치 에러가 나는 것. 

  test('Signup 테스트', async () => {
    const email = 'test@gmail.com';
    const name = 'test user';
    const password = 'password123';
    const birthday = '1990-01-01';
    const gender = 'male';
    const profile_url = 'https://example.com/profile';
    const createdAt = new Date('12 October 2022 00:00');
    const updatedAt = new Date('12 October 2022 00:00');

    const user = {
      user_id: 1,
      email,
      name,
      password,
      birthday,
      gender,
      profile_url,
      createdAt,
      updatedAt,
    };

    //실제로 테스트할 함수를 실행시켰을 때 결과값을 구하기
    mockUsersRepository.createUser.mockResolvedValue(user);
    const result = await userService.signup(
      email,
      name,
      password,
      birthday,
      gender,
      profile_url
    );

    //나와야하는 값
    const expectedUserInfo = {
      user_id: user.user_id,
      email: user.email,
      name: user.name,
      password: user.password,
      birthday: user.birthday,
      gender: user.gender,
      profile_url: user.profile_url,
      createdAt: new Date('12 October 2022 00:00'),
      updatedAt: new Date('12 October 2022 00:00'),
    };

    // console.log(result);
    expect(mockUsersRepository.createUser).toHaveBeenCalledWith(
      email,
      name,
      password,
      birthday,
      gender,
      profile_url
    );
    expect(result).toEqual(expectedUserInfo);
  });

이 문제 자체는 createdAt, updatedAt 코드를 삭제함으로써 간단하게 해결할 수 있는 문제였지만

테스트 코드를 짤 때 단순히 signup 함수가 잘 이루어지는가, 즉 repo에서 회원정보가 생성이 잘 되는가에 집중해야하는지, 내가 service에서 원한 기능이 test 되어야 하는지 헷갈리기 시작했다. 

 

그래서 기존에 test code를 작성한 수강생들과 의논도 해보고, 구글링도 해보며 내가 내린 결론은 signup service에서 궁극적으로 재가공된 정보를 출력하고 있다면 test code에서도 그 양식대로 정보가 반환되고 있는지 test 하는게 맞다 였다.

그래서 expectedvalue에 출력받을(재가공된) data 형식을 동일하게 입력해주어 오류를 해결했다.

 

2. login 기능에서 token 발급 기능 테스트하기

진짜 도대체 어떻게 접근해야될지 감도 안잡혔다. 

실제로 jwt.sign을 mock하여 token을 발행해야되는건가? 곰곰히 생각해봤다. 

우선 서비스에서 token에 관련된 어떤 기능이 있는지 부터 정리했다.

  • email로 user를 찾아서 user.user_id에 token을 할당한다
  • 할당된 토큰이 잘 반환되는지 확인한다
  • refresh token은 tokenrepository에 저장된다
  • 반환되야할 값은 accessObject, refreshToken

실제로 token을 발급받을 것은 아니기에 반환받을 token값을 문자열로 지정해준 후, **mockImplementation 함수를 사용하여 원하는 옵션의 token을 발급받는 코드를 짰다. 또한 refresh token은 repo에서 불러오도록하였다. 

userService.tokenRepository = mockTokenRepository;
  test('login 테스트', async () => {
    //email로 user를 찾아서 user.user_id에 token이 잘 할당되는지
    //할당된 토큰이 잘 반환되는지 확인해야함
    //email주소와 user_id를 임의로 지정한 후 user 객체를 만들어서 실험하자
    const email = 'test@gmail.com';

    const user = {
      user_id: 1,
      email,
    };

    //실제 토큰 값 대신 문자열로 확인할 예정
    const accessToken = 'access_token';
    const refreshToken = 'refresh_token';

    //우선 mock함수로 user를 불러온다
    mockUsersRepository.findOneUser.mockResolvedValue(user);
    //mockFn.mockImplementation(()=>{목 함수가 호출될 때 실행될 코드})
    //원하는 만료 시간을 가진 가짜 토큰을 반환하기 위한 함수
    jwt.sign.mockImplementation((payload, secret, options) => {
      //payload: {user_id:1}
      //secret: 'secret'
      //options: 토큰 옵션을 나타내는 객체
      if (options.expiresIn === '10s') {
        return accessToken;
      } else if (options.expiresIn === '7d') {
        return refreshToken;
      }
    });
    //생성된 refresh는 db에 저장하는 부분
    //함수가 refreshToken이라는 인자와 함께 1번 호출되는지 실험
    mockTokenRepository.setRefreshToken.mockResolvedValue();

    const expectedAccessObject = {
      type: 'Bearer',
      token: accessToken,
    };

    const expectedResponse = {
      accessObject: expectedAccessObject,
      refreshToken,
    };

    const result = await userService.login(email);

    console.log(result, expectedResponse);
    expect(mockUsersRepository.findOneUser).toHaveBeenCalledWith(email);
    expect(jwt.sign).toHaveBeenCalledWith({ user_id: 1 }, 'secret', {
      expiresIn: '10s',
    });
    expect(jwt.sign).toHaveBeenCalledWith({ user_id: 1 }, 'secret', {
      expiresIn: '7d',
    });
    expect(mockTokenRepository.setRefreshToken).toHaveBeenCalledWith(
      refreshToken,
      1
    );

    expect(result).toEqual(expectedResponse);
  });

그런데 이렇게 함수를 짜고 실행해주려 하니 

 

TypeError: jwt.sign.mockImplementation is not a function 99 | 100 | mockUsersRepository.findOneUser.mockResolvedValue(user); > 101 | jwt.sign.mockImplementation((payload, secret, options) => { | ^ 102 | if (options.expiresIn === '10s') { 103 | return accessToken; 104 | } else if (options.expiresIn === '7d') { at Object.mockImplementation (__tests__/unit/services/users.service.unit.spec.js:101:14)

 

에러가 났다. 에러의 이뉴는 jwt.sign이 mockImplementation을 지원하지 않기 때문인데, 그 이유는 내가 jwt.sign 함수를 mock하지 않았기 때문이였다. 아래와같이 jwt를 불러와 mock시켜주니 정상적으로 작동했다. 

const jwt = require('jsonwebtoken');
jest.mock('jsonwebtoken');

 

 

 

**mockImplementation함수란?

jest.fn()을 사용하여 생성한 mock 함수에 대해 mockImplementation 함수를 호출하면, 해당 목 함수가 호출 될 때 실행할 구현 코드를 지정할 수 있다. 즉 mock함수의 동작을 제어하는 데 사용된다. 

 

const mockFn = jest.fn();
mockFn.mockImplementation(() => {
  // 목 함수가 호출될 때 실행될 코드
});

 

 

**mockresolved 함수란?

mock 함수가 promise를 반환하는 경우 사용된다. 이 함수를 사용하면 mock 함수가 비동기적인 promise값을 반환하도록 설정할 수 있다. 

 

ex)아래의 fetchData 함수를 mock함수로 대체하고, 해당 모의 함수가 해결된 promise값을 반환하게 한다. 

const fetchData = () => {
  return new Promise((resolve) => {
    resolve('data');
  });
};

 

 

const fetchData = jest.fn().mockResolvedValue('data');

 

✅오늘 알게된점 및  추후 학습계획

기능을 test한다는 면에서 개념은 단순하지만 실제로 test를 만들땐 test의 의도나 방식에 따라 코드 구현이 

다양해질 수 있다는 것을 느꼈다. 실제로 구현해놓은 api가 복잡하면 복잡할 수록 test code도 복잡해지기에 

효율적이고 간결화된 code를 짤 수 있도록 더 노력해야될 것 같다. 

반응형