본문 바로가기

javascript/이외

[jest] 동일 파일 내의 함수 mocking하기


export async function genHash(user_pass: string, salt: string) {
  return (await pbkdf2(user_pass, salt, 100000, 64, 'sha512')).toString('hex');
}

export async function generatePassword(user_pass: string) {
  const salt = randomBytes(16).toString('hex'); // random salt 생성
  const hash = await genHash(user_pass, salt); // 해시 생성
  return `${hash}.${salt}`; // 해시 + salt 조합한 비밀번호 정보 반환
}

 패스워드 기능을 구현하기 위해 위와 같이 코드를 작성했다. 이때 generatePassword를 테스트할 때 실제로 genHash가 호출되는지 알아보고 싶어 jest.spyOn 함수를 이용하여 mocking을 시도했다.

describe('func generatePassword', () => {
  it('should call genHashFunction Once', async () => {
    //Arange
    const input = 'test';
    const genHashSpyFn = jest
      .spyOn(pwmodule, 'genHash')
      .mockImplementation((user_pass, salt) => {
        return new Promise((resolve) => {
          resolve(Buffer.from(user_pass + salt).toString('hex'));
        });
      });
    //Act
    const result = await pwmodule.generatePassword(input);
    console.log(result);
    //Assertion
    expect(genHashSpyFn).toHaveBeenCalled();
    //Restore
    genHashSpyFn.mockRestore();
  });

 나는 위 코드가 정상적으로 동작해야 한다고 생각했지만, 실제로는 다음과 같이 테스트에 실패했다.

실패한 테스트

 찾아본 내용에 따르면 여러가지 방법이 존재했다.

  1. password 모듈 내에서 import * as thisModule './password'; 같은 문장을 추가, thisModule.~ 형태로 접근한다. es6의 cyclic import 지원을 통해 가능하다고 한다.
  2. 함수들을 클래스로 묶는다. ( 동작하기는 하는데, 실제 코드를 변경함 )
  3. 함수들을 의존성 주입 형태로 처리한다. ( 실제 코드를 변경하므로 나쁨 )
  4. babel을 사용하는 경우 babel-rewire-plugin 사용
  5. 함수 표현식으로 변경

1 ~ 3번 선택지의 경우 테스팅을 위해 실제 동작에 필요하지 않은 사항을 추가하거나, 구현을 일부 변경해야 하는 번거로움이 있어 선택하고 싶지 않았다. 1번의 경우 실제로는 동작했다. 4번은 babel을 사용하지 않는 환경에서는 적용할 수 없는 방법이라 선택하기 껄끄러웠다.

결과적으로 실제 함수 구현에 큰 영향을 주지 않는 5번을 선택했고, 실제로 동작했다.

export const genHash = async (user_pass: string, salt: string) => {
  return (await pbkdf2(user_pass, salt, 100000, 64, 'sha512')).toString('hex');
};

export const generatePassword = async (user_pass: string) => {
  const salt = randomBytes(16).toString('hex'); // random salt 생성
  const hash = await genHash(user_pass, salt); // 해시 생성
  return `${hash}.${salt}`; // 해시 + salt 조합한 비밀번호 정보 반환
};

성공한 테스트

이외로 테스트해본 결과, 현재 mocking 대상이 되는 genHash가 함수 표현식(혹은 화살표 함수) 형태로 나타나야만 mocking이 제대로 동작했다.

 명확한 이유에 대해서는 잘 모르겠지만, jest의 mock 동작 방식에 해답이 있지 않을까 싶다. jest를 이용하여 다른 모듈에 있는 함수를 mocking하는 경우에는 함수 선언문으로 작성되어 있더라도 별 문제가 없었기 때문에 더 요상한 문제다...