우테코 테스트 코드로 본 Jest mock function - 1

김영우·2022년 11월 12일
3
post-thumbnail

mockQuestions 함수

const mockQuestions = (answers) => {
  MissionUtils.Console.readLine = jest.fn();
  answers.reduce((acc, input) => {
    return acc.mockImplementationOnce((question, callback) => {
      callback(input);
    });
  }, MissionUtils.Console.readLine);
};

위 함수는 우테코 5기 프리코스 3주차 미션의 테스트 코드에 있는 함수이다. 하나하나 분석해보자.


MissionUtils.Console.readLine = jest.fn();

함수 내부 첫 번째 줄만 봐도 벌써 알아야 할 것이 많다. 일단 readLine이 하는 역할 부터 알아보자.


우아한테크코스 5기 프리코스의 채점은 npm i를 통해 모듈을 다운로드 받고, npm test를 통해 jest 라이브러리의 테스트 코드를 모두 통과하는지 확인하는 방식으로 진행된다. 이때 다운로드 되는 모듈 중에는

이 친구가 포함돼있다. 미션을 채점하기 위해 우테코에서 직접 모듈을 만든 것으로 보인다. 이 중 readLine을 포함한 console.js를 보자.

import readline from "readline";

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

class Console {
  constructor() {}

  static readLine(query, callback) {
    if (arguments.length !== 2) {
        throw new Error("arguments must be 2.");
    }

    if (typeof query !== "string") {
        throw new Error("query must be string");
    }
        
    if (typeof callback !== "function") {
        throw new Error("callback must be function");
    }

    if (callback.length !== 1) {
        throw new Error("callback must have 1 argument");
    }

    rl.question(query, callback);
  }
}

console.jsreadLine과 관련된 친구들만 발췌해왔다.

코드의 가장 윗 부분을 보면 readline을 import해오고, createInterface 메서드를 사용해 무언갈 생성하여 r1에 대입함을 확인할 수 있다.

node.js 공식 문서를 확인해 본 결과 createInterface는 새로운 readline.Interface 인스턴스를 생성함을 알 수 있었다. 더 다룰 내용이 남았지만 잠시 넘어가겠다.


뒤이어 나오는 Console 클래스를 보자.

발췌한 코드 속 Console 클래스는 readLine이라는 static 메서드를 가지고 있다. readLine메서드는 querycallback이라는 두 가지 인자값을 넘겨받는다.

인자값을 넘겨받은 readLine메서드는 여러 예외 상황을 처리하고, 예외 상황에 걸리지 않는다면

r1.question(query, callback);

을 호출한다. 여기서 r1은 아까 언급했던 readlinecreateInterface메서드를 통해 생성된 readline.Interface이다. 이렇게 생성된 인스턴스의 question메서드를 호출하는 것이다.

node.js 공식문서에서 찾아본 결과 question메서드의 역할은 다음과 같다.

  1. query를 출력한다. (질문 역할)
  2. 사용자에게 query에 해당하는 응답을 입력받는다.
    • question메서드가 호출되면 입력이 발생하기 전까지 프로그램은 잠시 대기한다.
  3. 입력이 발생하면 callback의 첫번째 인자로 이를 전달한다.

따라서 이를 종합해본 결과 MissionUtils.Console.readLine은 사용자의 입력값을 처리하는 함수임을 알 수 있다.


혹시 까먹었을 수 있겠으나 우리의 목적은

MissionUtils.Console.readLine = jest.fn();

이 코드가 어떤 친구인지 분석하는 것이었다.

다시 본론으로 돌아와서 뒷 부분을 마저 분석해보자. 이번엔 jest.fn()이 어떤 역할을 하는지 알아볼 차례이다.

jest 공식 문서를 찾아본 결과 jest.fn()은 새로운, 사용되지 않은 mock function을 반환한다고 쓰여있었다.

또, mock function은 일종의 스파이 역할을 하는 함수라고 한다. 함수의 반환값을 통해 테스트를 하는 것이 아닌, 함수의 동작 과정을 테스트 하고 싶을 때 사용하는 친구이다.


이제 남은 뒷 부분을 살펴보자.

answers.reduce((acc, input) => {
  return acc.mockImplementationOnce((question, callback) => {
    callback(input);
  });
}, MissionUtils.Console.readLine);

mdn web docs에서 찾아본 결과 reduce함수는 위와 같은 인자값을 갖는다고 한다. 다시 우리가 볼 코드로 돌아와서 생각해보자.

우리가 가진 코드에서 reduce에 전달된 녀석은 callbackinitialValue이다. 또, callback에서 쓰인 인자는 accumulatorcurrentValue 이렇게 두 가지이다. 우리의 코드를 보면

(acc, input) => {
  return acc.mockImplementationOnce((question, callback) => {
    callback(input);
  });
};

이 친구가 callback이고, accaccumulator, inputcurrentValue이다.

MissionUtils.Console.readLine

그리고 이 친구가 initialValue이다.

함수의 동작을 순서로 나타내면 다음과 같다.

  1. MissionUtils.Console.readLinecallback의 첫번째 인수로 전달된다.
  2. mockImplementationOnce을 호출한 후 반환된 값을 acc에 누적한다.
  3. answer의 길이만큼 2번을 반복한다.

이제 거의 다 왔다. mockImplementationOnce을 찾아보자.

jest 공식 문서를 찾아본 결과 mockImplementationOnce는 mock function을 한번 실행 시켜주는 역할을 수행함을 알 수 있었다.


const mockQuestions = (answers) => {
  MissionUtils.Console.readLine = jest.fn();
  answers.reduce((acc, input) => {
    return acc.mockImplementationOnce((question, callback) => {
      callback(input);
    });
  }, MissionUtils.Console.readLine);
};

이제 각각 분석한 코드를 하나로 합쳐서 보자.

위 함수의 실행 순서는 다음과 같다.

  1. answers를 인자로 입력 받는다.
  2. MissionUtils.Console.readLine을 mock function으로 만든다.
  3. answers의 요소들 각각을 mock function에 들어가는 콜백 함수의 인자로 전달한다.

종합해보자면 위 함수에 answers 배열을 인자로 넘겨 호출하면 MissionUtils.Console.readLine의 콜백함수의 인자에 answers 배열의 각 요소를 넘겨주는 역할을 한다. 이때, 콜백함수의 인자는 사용자의 입력값을 의미하므로 이 함수의 실행은 answers 배열을 통해 임의대로 사용자 입력을 조작할 수 있게 해준다.


다음 포스팅에는 테스트 코드로 주어진

const mockRandoms = (numbers) => {
  MissionUtils.Random.pickUniqueNumbersInRange = jest.fn();
  numbers.reduce((acc, number) => {
    return acc.mockReturnValueOnce(number);
  }, MissionUtils.Random.pickUniqueNumbersInRange);
};

mockRandoms를 분석해보겠다.

profile
불편한 일들을 개발로 풀어내고 싶은 프론트엔드 개발자입니다!

0개의 댓글