우테코 프리코스 2주차(Jest)

2023년 11월 2일

우테코 프리코스 2주차가 지났다. 이번 프리코스에서 공부했던 것을 기록하려고 한다.

Jest

Jest는 단순성에 중점을 둔 자바스크립트 테스트 프레임워크다.
Jest의 기본 기능과 우테코에 있던 테스트 코드를 중점으로 정리하려 한다.

Macthers

값을 테스트하는 가장 간단한 방법으로 동일성을 테스트하는 것이다.

expect(value).toBe(value)

test.js
test('two plus two is four', () => {
    expect(2 + 2).toBe(4);
});

expect(2 + 2)는 "기대(expectation)" 객체를 반환한다. 일반적으로 기대 객체에서 matchers를 호출한다. 이 코드에서는 toBe(4)가 matchers 역할을 한다.

expect(value).toEqual(value)

test.js
test('object assignment', () => {
    const data = { one: 1 };
    data['two'] = 2;
    expect(data).toEqual({ one: 1, two: 2 });
});

객체의 값을 확인하려면 toEqual 을 사용한다. toEqual은 객체나 배열의 모든 필드를 재귀적으로 확인한다.

expect(value).not(value)

not을 사용하여 일치하는 항목의 반대를 테스트할 수도 있다.

Exceptions

특정 함수가 호출될 때 에러를 throw하는지 테스트하려면 toThrow를 사용한다.

expect(value).toThrow(error?)

test.js
function compileAndroidCode() {
  throw new Error('you are using the wrong JDK!');
}

test('compiling android goes as expected', () => {
  expect(() => compileAndroidCode()).toThrow();
  expect(() => compileAndroidCode()).toThrow(Error);
});

예외를 던지는 함수는 래핑 함수 내에서 호출해야 하며, 그렇지 않으면 thThrow assertion이 실패한다.
(assertion: 프로그램 안에 추가하는 참, 거짓을 미리 가정하는 문)

Mock Functions (모의 함수)

모의 함수를 사용하면 함수의 실제 구현을 무시하고, 함수를 대체할 수 있다. 테스트하려는 코드가 의존하는 부분을 생성하기 어려울 때 사용한다.

jest.fn(implementation?)

사용되지 않은 새 모의 함수를 반환한다. 선택적으로 모의 구현을 받는다.
간단하게 가짜 함수라 할 수 있다.

test.js
const mockFn = jest.fn();

Mock Return Values

Mock 함수를 사용하여 테스트 중에 테스트 값을 코드에 삽입할 수 있다.

test.js
const myMock = jest.fn();
console.log(myMock());
// > undefined

myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);

console.log(myMock(), myMock(), myMock(), myMock());
// > 10 , 'x', true, true

mockFn.mockReturnValue(value)

모의 함수가 호출될 때마다 반환될 값을 받는다.

mockFn.mockReturnValueOnce(value)

모의 함수를 한 번 호출할 때 반환될 값을 받는다. 모의 함수에 대한 연속적인 호출이 다른 값을 반환하도록 연쇄할 수 있다. 더 이상 사용할 값이 없는 경우 mockReturnValue로 지정된 값을 반환한다.

test.js
const myMock = jest.fn();
myMock.mockReturnValue(1);
myMock.mockReturnValueOnce(2);

console.log(myMock(), myMock(), myMock()); 
// > 2 1 1

위 코드를 보면 mockReturnValueOnce의 우선 순위가 더 높다. mockReturnValueOnce의 저장된 값을 먼저 반환하고, 저장된 값들을 전부 반환하면 mockReturnValue의 값을 반환한다.

Mock Implementations

반환 값을 지정하는 기능을 넘어 모의 함수의 구현을 완전히 대체하기 위한 기능
jest.fn 또는 mockImplementation를 이용한다.

test.js
const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// true

mockFn.mockImplementation(fn)

모의 구현으로 사용해야 하는 함수를 받는다. 모의 자체는 여전히 자신으로부터 들어오는 모든 호출과 인스턴스를 기록하며, 모의가 호출될 때 구현도 실행된다.
jest.fn과 차이점으로 mock이 호출될 때 구현도 실행된다는 점이라는 데 유의미한 차이는 아닌거 같다.
jest 홈페이지에서 "jest.fn(implementation)jest.fn().mockImplementation(implementation)의 축약어다." 라고 하니 차이점은 없다고 봐도 무방한거 같다.

test.js
// foo.js
module.exports = function () {
    ...
};


// test.js
jest.mock('../foo'); 
const foo = require('../foo');

// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42

mockFn.mockImplementationOnce(fn)

모의 함수에 대한 한 번의 호출에 대해 모의의 구현으로 사용될 함수를 받는다. 여러 함수 호출이 서로 다른 결과를 생성하도록 연쇄할 수 있다.

test.js
const myMockFn = jest
  .fn()
  .mockImplementationOnce(cb => cb(null, true))
  .mockImplementationOnce(cb => cb(null, false));

myMockFn((err, val) => console.log(val));
// > true

myMockFn((err, val) => console.log(val));
// > false

모의 함수에 mockImplementationOnce 로 정의된 구현이 부족하면 jest.fn 으로 설정된 기본 구현(정의된 경우)를 한다.

test.js
const myMockFn = jest
  .fn(() => 'default')
  .mockImplementationOnce(() => 'first call')
  .mockImplementationOnce(() => 'second call');

console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'

jest.spyOn(object, methodName)

jest.fn과 유사한 모의 함수를 생성하지만, object[methodName]에 대한 호출도 추적한다. Jest 모의 함수를 반환한다.

우테코 테스트 코드 분석

test.js
const mockQuestions = (inputs) => {
  MissionUtils.Console.readLineAsync = jest.fn();

  MissionUtils.Console.readLineAsync.mockImplementation(() => {
    const input = inputs.shift();
    return Promise.resolve(input);
  });
};

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

const getLogSpy = () => {
  const logSpy = jest.spyOn(MissionUtils.Console, "print");
  logSpy.mockClear();
  return logSpy;
};

describe('test' , () => {
    test('test', () => {
        ...
        const logSpy = getLogSpy();
        expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output));
    });
});

mockQuestions

MissionUtils.Console.readLineAsync를 모의 함수로 선언하고, 매개변수 inputs의 맨 왼쪽 값을 Promise resolve값으로 반환한다.
코드 내에서 MissionUtils.Console.readLineAsync가 호출되면 맨 왼쪽 값이 반환.
예를 들어 [1, 2, 3]이 매개변수 inputs로 주어지면 코드에서 MissionUtils.Console.readLineAsync이 호출 시 promise resolve 1이 반환되며 inputs에는[2, 3]이 남는다.

mockRandoms

MissionUtils.Random.pickNumberInRange을 모의 함수로 선언하고, 매개변수 numbers를 모두 값을 모의 함수 MissionUtils.Random.pickNumberInRange의 리턴 값을 mockReturnValueOnce(number)으로 설정한다.
코드에서 MissionUtils.Random.pickNumberInRange 호출될 때마다 저장된 값이 차례대로 반환된다.

getLogSpy

MissionUtils.Console.print를 추적한다. 이를 이용해 출력한 문자와 예상 출력 문자를 비교해 테스트한다.

expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output))

toHaveBeenCalledWith()을 사용하면 특정 인수를 사용하여 모의 함수가 호출되었는지 확인할 수 있다.
expect.stringContaining(output)을 이용해 logSpy에서 ouput이 포함되어 있는 문자열이 호출되었는지를 테스트한다.


댓글