우테코 프리코스 2주차(Jest)
우테코 프리코스 2주차가 지났다. 이번 프리코스에서 공부했던 것을 기록하려고 한다.
Jest
Jest는 단순성에 중점을 둔 자바스크립트 테스트 프레임워크다.
Jest의 기본 기능과 우테코에 있던 테스트 코드를 중점으로 정리하려 한다.
Macthers
값을 테스트하는 가장 간단한 방법으로 동일성을 테스트하는 것이다.
expect(value).toBe(value)
test('two plus two is four', () => {
expect(2 + 2).toBe(4);
});
expect(2 + 2)
는 "기대(expectation)" 객체를 반환한다. 일반적으로 기대 객체에서 matchers를 호출한다. 이 코드에서는 toBe(4)
가 matchers 역할을 한다.
expect(value).toEqual(value)
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?)
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?)
사용되지 않은 새 모의 함수를 반환한다. 선택적으로 모의 구현을 받는다.
간단하게 가짜 함수라 할 수 있다.
const mockFn = jest.fn();
Mock Return Values
Mock 함수를 사용하여 테스트 중에 테스트 값을 코드에 삽입할 수 있다.
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
로 지정된 값을 반환한다.
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
를 이용한다.
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)
의 축약어다." 라고 하니 차이점은 없다고 봐도 무방한거 같다.
// 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)
모의 함수에 대한 한 번의 호출에 대해 모의의 구현으로 사용될 함수를 받는다. 여러 함수 호출이 서로 다른 결과를 생성하도록 연쇄할 수 있다.
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
으로 설정된 기본 구현(정의된 경우)를 한다.
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 모의 함수를 반환한다.
우테코 테스트 코드 분석
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
이 포함되어 있는 문자열이 호출되었는지를 테스트한다.