[테스트 with Jest] 섹션 1. Jest 배워보기
Jest
자바스크립트 제일 유명한 테스트 프레임 워크
프레임워크란? 테스트에 필요한 모든것을 갖춰두었다.
babel, typescript, Node, React, Angular, Vue 테스트 할 수 있다.
Jest (Javascript + Test)
대체제
Vitest(Vite), Jasmine, Mocha + Sinon + Chai 등..
테스트 커버리지
테스트 코드가 내코드를 얼마나 커버하고 있나
테스트를 왜 해야하는지 ?
- 예전에 났던 코드가 또 오류가 나는경우를 위해 회귀 테스트를 해야한다.
회귀테스트 - 오류난 부분에서 오류 안나게 테스트하는 것 - 코드가 복잡한데 많이 바뀌어야하는경우
- 하나의 코드를 수정했더니 import 한 다른곳에서 에러가 나는 경우
- 장시간에 걸쳐 내가 유지보수 해야하는 경우
TDD - Test Driven Development (테스트 기반 개발)
BDD - Behavior Driven Development (행동 기반 개발)
테스트 종류
유닛, 통합, E2E(일련의 프로세스 테스트, 사용자의 행동을 그대로 테스트, 회원가입 로그인 등등 )
테스트 원칙
- 테스트는 틈틈히 추가하자
- 도움이 되는 테스트하기
Jest 설치 및 ESM 세팅
node프로젝트 만들기
npm init -y
jest 개발모드 설치하기
테스트하는 툴이기때문에 배포시에는 필요없기때문에 개발모드로 설치한다.
npm install jest -D
src 폴더와 동일한 위치에 test 폴더를 만들거나,
테스트 할 파일과 동일한 위치에 test 파일(toBe.spec.js)을 만들어서 사용한다.
테스트 파일명은 이름.spec.js, 이름.test.js로 규칙이 정해져있다.
spec, test를 변경하고 싶으면 jest.config.js 파일에서 testRegex에 명시해 주면된다.
// toBe.js
export function sum(x, y) {
return x + y
}
// toBe.spec.js
import sum from './toBe';
test("sum함수는 두 숫자를 더해야 한다.", () => {
expect(sum(1, 2)).toBe(3);
}); // test, expect, toBe 함수는 jest설치하면 전역적으로 사용할 수 있다.
터미널에서 npx jest 를 입력해 테스트를 할 수 있다.
ecma 스크립트를 사용한 경우 아직 실험적인 기능이여서
'NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules' 요걸 붙여줘야한다.
https://jestjs.io/docs/ecmascript-modules
NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npx jest
package.json에 type 추가
type: 'module'
타입스크립트 세팅
패키지 설치하기
npm i -D ts-jest @types/jest
설정파일 만들기 jest.config.js 가 생긴다.
설정파일 만들지 않고 package.json에서 관리할수도 있다.
npx ts-jest config:init
package.json에서 type: module 빼고 npx jest로 실행하기
타입스크립트는 ecma스크립트가 아니라서
toBe, not, jest 익스텐션
not.toBe()
test("sum함수는 두 숫자를 더해야 한다.", () => {
expect(sum(1, 2)).not.toBe(3); // 기대하는 값이 3이 아니다.
});
settings.json
VS코드에서 자체적으로 테스트를 실행해주게 세팅
{
"jest.pathToJest": "**/node_modules/.bin/jest",
"jest.pathToConfig": "**/jest.config.js",
"jest.showCoverageOnLoad": true,
}
js 확장프로그램에서 jest 설치하기
설치하면 아래 Jest 라고 나타난다.
테스트코드로 가면 왼쪽에 체크 아이콘이 뜨고 클릭하면 테스트가 실행된다.
toStrictEqual, toMatchObject
객체를 테스트하기 위한 메소드
객체끼리 비교할때는 toStrictEqual 사용 , 배열비교도 toStrictEqual 사용
객체는 참조형이라 비교연산자로 간단히 비교할수없기 때문에 toBe로는 테스트할 수 없다.
expect(obj).toStrictEqual({
a: 'aaa'
});
클래스 비교할때는 toMatchObject
test('클래스 비교는 toMatchObject로 해야 한다', () => {
expect(obj('hello')).not.toStrictEqual({ a: 'hello' });
expect(obj('hello')).toMatchObject({ a: 'hello' });
});
toHaveBeenCalled 시리즈와 jest.fn, jest.spyOn
fn을 붙인애들을 spy라고 한다.
Jest.fn가 자바스크립트가 해주지 않는기능을 수행해준다. 몇번 함수가 호출되었는지, 어떤 인수로 호출을 했는지 기록해준다.
toHaveBeenCalled
함수가 호출이 되었는지 테스트
실행하는 함수를 jest.fn로 spy객체를 만들고, spy객체를 호출한다.
함수호출만 테스트하는게 의미없을 수 있다. toHaveBeenCalledTimes, toHaveBeenCalledWith 더 유용하게 사용될 수있다.
test('sum 함수가 호출되었다', () => {
const sumSpy = jest.fn(sum);
sumSpy(1, 2);
expect(sumSpy).toHaveBeenCalled();
});
toHaveBeenCalledTimes
함수가 몇번 호출되었는지 테스트
test('sum 함수가 1번 호출되었다', () => {
const sumSpy = jest.fn(sum);
sumSpy(1, 2);
expect(sumSpy).toHaveBeenCalledTimes(1);
});
toHaveBeenCalledWith
어떤 인수와 함수가 실행되었는지 테스트
test('sum 함수가 1,2와 함께 호출되었다', () => {
const sumSpy = jest.fn(sum);
sumSpy(1, 2);
expect(sumSpy).toHaveBeenCalledWith(1, 2);
});
object 내 함수를 테스트하고 싶은경우
1. 기존과 동일하게 obj.minus로 사용해서 테스트
test('obj.minus 함수가 1번 호출되었다(spy함수 생성)', () => {
const minusSpy = jest.fn(obj.minus);
minusSpy(1, 2);
expect(minusSpy).toHaveBeenCalledTimes(1);
});
2. spyOn을 이용해서 테스트
spyOn을 이용하면 스파이객체를 테스트하는 객체에 심는작업을 하는 것이다.
test('obj.minus 함수가 1번 호출되었다(spy 삽입)', () => {
jest.spyOn(obj, 'minus');
const result = obj.minus(1, 2);
console.log(obj.minus);
expect(obj.minus).toHaveBeenCalledTimes(1);
expect(result).toBe(-1);
});
3. 테스트이름이 겹치면 하나를 실행할때 같이 실행될수도 있다. 다르게 사용하는걸 권장한다.
test('obj.minus 함수가 1번 호출되었다', () => {
...
});
test('obj.minus 함수가 1번 호출되었다', () => {
...
});
mockImplementation, mockReturnValue
mockImplementation
데이터 페치하는 함수를 테스트하는경우 호출하고 싶진 않은데 몇번호출했는지 확인하고 싶은경우 사용한다.
mockImplementation 인수로 들어가는 함수에 mock 데이터를 넣을 수도 있다.
(호출은 하지 않지만, 데이터는 내가원하는 mock데이터가 나온다 )
test('obj.minus에 스파이를 심고 실행도 안되게', () => {
spyFn = jest.fn(minus).mockImplementation(() => 3);
const result = spyFn(1, 2);
expect(spyFn).toHaveBeenCalledTimes(1);
expect(result).toBe(3);
});
함수에서 사용하는 인수도 사용가능
spyFn = jest.fn(minus).mockImplementation((a, b) => a + b);
mockImplementationOnce
한번만 호출하는 것
test('obj.minus에 스파이를 심고 리턴값이 서로 다르게 나오게', () => {
spyFn = jest.fn(minus)
.mockImplementationOnce((a, b) => a + b)
.mockImplementationOnce(() => 5)
const result1 = spyFn(1, 2);
const result2 = spyFn(1, 2);
const result3 = spyFn(1, 2);
expect(spyFn).toHaveBeenCalledTimes(3);
expect(result1).toBe(3);
expect(result2).toBe(5);
expect(result3).toBe(-1);
});
mockImplementationOnce를 사용한 첫번째 호출, 두번째 호출은 mock 데이터가 나오고
이후에는 (result3 부터) 기존함수의 리턴값이 나온다.
Once를 사용한 이후에 일정한 mock데이터로 나오게 하려면 mockImplementation를 사용하면 된다.
test('obj.minus에 스파이를 심고 리턴값이 서로 다르게 나오게', () => {
spyFn = jest.fn(minus)
.mockImplementationOnce((a, b) => a + b)
.mockImplementationOnce(() => 5)
.mockImplementation(() => 4)
const result1 = spyFn(1, 2);
const result2 = spyFn(1, 2);
const result3 = spyFn(1, 2);
const result4 = spyFn(1, 2);
expect(spyFn).toHaveBeenCalledTimes(4);
expect(result1).toBe(3);
expect(result2).toBe(5);
expect(result3).toBe(4);
expect(result3).toBe(4);
});
mockReturnValue
mock 함수를 리턴하는게 아니라 값만 리턴하는 함수
test('obj.minus에 스파이를 심고 리턴값이 다르게 나오게(mockReturnValue)', () => {
spyFn = jest.fn(minus)
.mockReturnValue(5)
const result1 = spyFn(1, 2);
expect(spyFn).toHaveBeenCalledTimes(1);
expect(result1).toBe(5);
});
mockReturnValueOnce
한번만 호출한다.
비동기함수 테스트
Resolves를 사용하는경우는 return을 써줘야한다.
그래야 비동기함수가 다 끝나고 테스트가 실행된다.
사용하지 않으면 항상 true값이 나온다.
// return 사용
test('okPromise 테스트', () => {
return expect(okPromise()).resolves.toBe('no')
})
// return 사용안함
test('okPromise 테스트', () => {
expect(okPromise()).resolves.toBe('no')
})
성공한 promise는 resolve, then
// resolve
test('okPromise 테스트', () => {
const okSpy = jest.fn(okPromise);
return expect(okSpy()).resolves.toBe('ok')
})
//then
test("okPromise 테스트", () => {
const okSpy = jest.fn(okPromise);
return okSpy().then((result) => {
expect(result).toBe("no");
});
});
//async
test("테스트", async () => {
const okSpy = jest.fn(okPromise);
const result = await okSpy();
expect(result).toBe("ok");
});
실패한 promise는 reject, catch 를 사용한다.
// catch
test("noPromise 테스트", () => {
const okSpy = jest.fn(noPromise);
return okSpy().catch((result) => {
expect(result).toBe("no");
});
});
// rejects
test("noPromise 테스트", () => {
const okSpy = jest.fn(noPromise);
return expect(okSpy()).rejects.toBe("no");
});
// trycatch
test("테스트", async () => {
const okSpy = jest.fn(noPromise);
try {
const result = await okSpy();
} catch (error) {
expect(error).toBe("no");
}
});
async, await으로 사용하면 return문 필요없다.
오류나는것은 try, catch문을 사용해서 잡으면 된다.
mockResolvedValue
mockResolvedValueOnce 요런것도 다 사용가능하다.
import * as fns from './asyncFunction';
jest.spyOn(fns, 'okPromise').mockReturnValue(Promise.resolve('test'));
// mockReturnValue로 사용할 수있는데 더 간단하게 사용하는 방법
jest.spyOn(fns, 'okPromise').mockResolvedValue('test')
mockRejectedValue
import * as fns from './asyncFunction';
jest.spyOn(fns, 'okPromise').mockReturnValue(Promise.reject('test no'));
// mockReturnValue로 사용할 수있는데 더 간단하게 사용하는 방법
jest.spyOn(fns, 'okPromise').mockRejectedValue('test')
콜백함수 테스트
done 매개변수를 사용해서 테스트할수있다.
done 사용하지 않으면 모든값이 true
test("콜백함수 테스트", (done) => {
timer((message: string) => {
expect(message).toBe("dd");
done();
});
});
에러 테스트
Expect(error()) 에서 에러가 나기때문에 toThrow가 실행이 안된다
expect 안에 함수로 감싸주면 된다.
test("에러가 잘 난다.", () => {
expect(error()).toThrow(Error);
}); // x
test("에러가 잘 난다.", () => {
expect(() => error()).toThrow(Error);
}); //o
Try catch 문 이용하는경우
try {
error();
} catch (error) {
expect(error).toStrictEqual(new Error());
}
// error가 객체이기때문에 toStrickEqual을 사용해서 비교한다.
mockClear, mockReset, mockRestore
스파이 객체가 자바스크립트 코드에서 여러번 사용되는경우
스파이 함수가 유지되어서 toHaveBeenCalledTimes 함수로 몇번 호출되었는지 확인하는경우 맞지 않을 수있다.
이런 경우 스파이함수를 변수에 담고
함수가 끝나는 시점에 클리어하면 된다.
spyFn.mockClear(); // Times, With 초기화
spyFn.mockReset(); // mockClear + mockImplementation(() => {}) 빈함수로
spyFn.mockRestore(); // 전부없애버림
jest 에서 제공하는 리셋 함수가 있다.
모든테스트를 clear, reset, restore 할 수 있다.
jest.clearAllMocks();
jest.resetAllMocks();
jest.restoreAllMocks();
테스트 라이프사이클
beforeAll, beforeEach, afterEach, afterAll
afterEach
매번 실행 후에
beforeEach
매번 실행 전에
Ex) 변수를 초기화 해야한다거나
beforeAll
모든 테스트 전에
ex) 디비를 불러온다거나
afterAll
모든 테스트 후에
그룹핑 describe
afterAll 과 같은 라이프사이클 함수를 사용해야하는 경우
파일의 절반만 적용하고 싶을때 그룹핑을 사용한다.
적용할 코드만 파일로 나눠서 적용해도 된다.
let beforeEachCount = 0;
let afterEachCount = 0;
describe('beforeEach, afterEach 적용', () => {
beforeEach(() => {
console.log('beforeEach', )
})
afterEach(() => {
console.log('beforeEach', )
})
test코드...
})
describe에도 beforeEach가 있고 outside에도 beforeEach가 있는경우
Describe 테스트 코드는 둘다 실행된다. outside 먼저실행 -> describe안에 함수 실행
skip, todo
스킵할수있다.
지금당장 테스트할 건 아닌데 나중에 테스트 만들어야하지 하는것 todo
test.skip
xtest //skip이다
xit //skip이다
test.todo
it
test 대신에 it을 사용할 수있다.
it('테스트 코드', () => {
})
날짜 / 시간 테스트 + expect.assertions
같은 코드를 toBe에 넣어서 테스트해도 자바스크립트 실행시간이 있기 때문에 테스트가 실패한다.
// date.ts
export function after3days() {
const date = new Date();
date.setDate(date.getDate() + 3);
return date;
}
// date.spec.ts
import { after3days } from "./date";
test("3일 후를 리턴한다", () => {
const date = new Date();
date.setDate(date.getDate() + 3);
expect(after3days()).toBe(date); // 같은 코드를 toBe에 넣었는데도 실패
});
useFakeTimers를 이용해 가짜 타이머를 만들어서 테스트해준다.
*주의 할 점
끝나면 useRealTimers로 되돌려 놔야한다.
test("3일 후를 리턴한다", () => {
jest.useFakeTimers().setSystemTime(new Date(2024, 3, 9));
console.log(new Date());
expect(after3days()).toStrictEqual(new Date(2024, 3, 12));
jest.useRealTimers();
});
afterEach를 사용하면 간단하게 테스트가 끝날때마다 실행할 수 있다.
afterEach(() => {
jest.useRealTimers();
});
jest fakeTimer 기능이 많다.
https://jestjs.io/docs/jest-object#jestusefaketimersfaketimersconfig
jest.runAllTicks()
바로 promise 반환할 수 있는 메소드
jest.runAllTimers()
바로 setTimer 가 실행할 수 있게 하는 메소드
jest.runAllImmediates()
바로 Immediate 반환하는 메소드
jest.advanceTimersByTime
시간 빠르게 가도록 하는 메소드
jest.clearAllTimers
타이머 없애는 메소드
jest.getTimersCount()
타이머 몇개인지 카운트 하는 메소드
jest.getRealSystemTime()
useFakeTimer를 사용해서 가짜시간을 설정하면 실제시간을 볼 수 있는방법은 jest.getRealSystemTime 밖에 없다.
테스트 타임 기준은 5초
더 늘리고 싶다면 test함수에 테스트 할 초를 설정할 수 있다.
test("콜백함수 테스트", (done) => {
timer((message: string) => {
expect(message).toBe("dd");
done();
});
}, 20_000);
테스트 시간을 늘려주는거 보다 타이머시간을 빨리가게 하는것을 추천한다. runAllTimers();
runAllTimers 를 사용할 때는 useFakeTimers를 사용해야한다.
// runAllTimers
test("콜백함수 테스트", (done) => {
jest.useFakeTimers();
timer((message: string) => {
expect(message).toBe("dd");
done();
});
jest.runAllTimers();
});
advanceTimersByTime을 사용해서 타이머를 당길 수도 있다.
*주의할 점
Callback 함수보다 advanceTimersByTime로 지정한 시간이 작으면 실패한다. 같거나 더 많아야한다.
//advanceTimersByTime
test("콜백함수 테스트", (done) => {
jest.useFakeTimers();
timer((message: string) => {
expect(message).toBe("dd");
done();
});
jest.advanceTimersByTime(6000);
});
expect.assertions(0);
expect 구문이 몇개인지 확인할 수있다.
비동기 함수에 넣어서 실행되었는지 확신을 얻는것이 좋다.
.toBeCloseTo()
부동 소수점 숫자를 대략적으로 같은지 비교하는데 사용합니다.
test('adding works sanely with decimals', () => {
expect(0.2 + 0.1).toBe(0.3); //fails 0.30000000000000004
});
test('adding works sanely with decimals', () => {
expect(0.2 + 0.1).toBeCloseTo(0.3); // success
});
호출순서, mock 객체
spy객체를 사용하면 mock생성자가 생긴다.
[Function: mockConstructor] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
mock: [Getter/Setter],
mockClear: [Function (anonymous)],
mockReset: [Function (anonymous)],
mockRestore: [Function (anonymous)],
mockReturnValueOnce: [Function (anonymous)],
mockResolvedValueOnce: [Function (anonymous)],
mockRejectedValueOnce: [Function (anonymous)],
mockReturnValue: [Function (anonymous)],
mockResolvedValue: [Function (anonymous)],
mockRejectedValue: [Function (anonymous)],
mockImplementationOnce: [Function (anonymous)],
withImplementation: [Function: bound withImplementation],
mockImplementation: [Function (anonymous)],
mockReturnThis: [Function (anonymous)],
mockName: [Function (anonymous)],
getMockName: [Function (anonymous)]
}
mock을 찍어보면 객체가 나오는데
Calls는 몇번, 어떤인수랑 호출되었는지 알수있고
invocationCallOrder 함수 호출 순서 배열
{
calls: [ [], [] ],
contexts: [ undefined, undefined ],
instances: [ undefined, undefined ],
invocationCallOrder: [ 1, 4 ],
results: [
{ type: 'return', value: undefined },
{ type: 'return', value: undefined }
],
lastCall: []
}
.toBeLessThan
호출순서가 낮은거 확인할 때
expect(firstSpy.mock.invocationCallOrder[0]).toBeLessThan(
secondSpy.mock.invocationCallOrder[0]
);
.toBeGreaterThan
호출 순서가 높은거 확인 할 때
expect(firstSpy.mock.invocationCallOrder[1]).toBeGreaterThan(
thirdSpy.mock.invocationCallOrder[0]
);
jest-extended
사용하는 이유
복잡한 코드가 가독성이 떨어지기 때문에
jest extended 설치하기
npm install jest-extended -D
환경설정
참고 https://jest-extended.jestcommunity.dev/docs/getting-started/setup
타입스크립트 사용하는경우 vscode에서 인식을 못하는경우가 있어서
Global.d.ts
import "jest-extended";
Tsconfig.ts 파일 설정
{
"files": ["global.d.ts"]
}
testSetup.js 파일 만들기
src/ 아래에 만들어준다.
const matchers = require('jest-extended');
expect.extend(matchers);
Jest.config.js파일에 해당내용 추가
rootDir: ".",
setupFilesAfterEnv: ["./src/testSetup.js"],
extended 적용 전
expect(firstSpy.mock.invocationCallOrder[0]).toBeLessThan(
secondSpy.mock.invocationCallOrder[0]
);
expect(firstSpy.mock.invocationCallOrder[1]).toBeGreaterThan(
thirdSpy.mock.invocationCallOrder[0]
);
extended 적용 후
간결해진 코드
expect(firstSpy).toHaveBeenCalledBefore(secondSpy);
expect(thirdSpy).toHaveBeenCalledAfter(firstSpy);
mock 객체의 활용
toHaveBeenCalled를 사용하면 어떤 인수와 함수가 호출되었는지 확인할수 있는데
인수가 엄청 복잡한 객체인데 객체의 값하나만 비교하는경우는 mock 객체의 calls를 이용해 비교할 수 있다,
test("객체비교는 요렇게", () => {
const fn = jest.fn();
fn({
a: {
b: {
c: "hello",
},
},
f: ["f"],
});
expect(fn.mock.calls[0][0].a.b.c).toBe("hello");
});
모듈 모킹하기 (jest.mock)
모듈의 메소드를 한번에 모킹할수 있는 방법
기존에는 요렇게 사용해서 번거로웠다
// module.ts
export const obj = {
method() {
return "123";
},
method1() {
return "123";
},
method2() {
return "123";
},
method3() {
return "123";
},
method4() {
return "123";
},
prop: "change me",
};
// module.spec.ts
import { obj } from "./module";
test("모듈을 전부 모킹", () => {
jest.spyOn(obj, "method");
jest.spyOn(obj, "method1");
jest.spyOn(obj, "method2");
jest.spyOn(obj, "method3");
jest.spyOn(obj, "method4");
});
Jest.mock을 사용해서 파일을 불러와 한번에 모킹할 수 있다.
import { obj } from "./module";
jest.mock("./module");
test("모킹 확인", () => {
console.log(obj);
});
모든메소드가 모킹 된 걸 확인할 수 있다.
jest.mock을 이용해서 다른데이터로 모킹할 수 있다.
import 한 obj 객체를 {a: 'b'} 값으로 변경하는 코드
import {obj} from './module'
jest.mock('./module', () => {
return {
obj: {a: 'b'},
prop: 'hello'
}
})
jest.mock을 이용해서 특정 메소드 하나만 모킹하기
메소드 하나만 모킹하는경우는 jest.spyOn(obj, "method"); 을 사용하는게 적절하다.
Jest.requireActual을 사용해 실제 모킹할 함수 값을 가져올 수 있다.
useFakeTimer를 사용해서 가짜시간을 설정하면 실제시간을 볼 수 있는방법은 jest.getRealSystemTime 밖에 없다.
jest.mock("./module", () => {
return {
obj: {
...jest.requireActual("./module").obj,
method3: jest.fn(),
},
prop: "hello",
};
});
Jest.mock은 호이스팅 된다.
import 구문보다 앞에 있거나 import 바로 뒤에 작성해서 사용하는게 좋다.
파일을 통째로 갈아끼우고 싶은경우 mocks 폴더를 만든다.
모든테스트에 공통적으로 모킹을 해야하는경우 사용한다.
// __mocks__/module.ts
export const obj = {
method: jest.fn(),
method1: jest.fn(),
method2: jest.fn(),
method3() {
return "123";
},
method4: jest.fn(),
prop: "change me",
};
jest.mock("./module");
import { obj } from "./module";
test("모킹 확인", () => {
console.log(obj);
});
메서드가 아닌 속성 수정하기
replaceProperty를 사용해 속성을 수정할 수 있다.
test("모킹 확인", () => {
jest.replaceProperty(obj, "prop", "replaced");
console.log(obj);
});
node_modules 모킹하기
import axios from "axios";
jest.mock("axios");
test("axios 모킹", () => {
console.log(axios);
});
Node_modules 와 형제 폴더로 mocks를 만들어 통째로 모킹할 수 있다.
// __mocks__/axios.ts
export default {
haha: "test",
};
*주의할 점
jest.config.js에 rootDir을 기준으로 테스트를 진행하는데 rootDir을 src 설정하고
Node_modules 폴더가 src 폴더와 동일한 위치에 위치해있으면 테스트가 되지 않는다.
.로 변경해서 모든 폴더에 접근할 수있게 해야한다.
/** @type {import('ts-jest').JestConfigWithTsJest} **/
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
rootDir: ".",
setupFilesAfterEnv: ["./src/testSetup.js"],
};
.toBeDefined()
정의 되어있으면 된다.
requireActual로 원본 모듈 가져오기
모듈이 객체든 단일함수든 클래스든 전부 모킹된다.
// mockFunc.ts
export default function single() {
console.log("hi");
}
export function double() {
console.log("double");
}
export default class A {
methodA() {
console.log("원본");
}
methodB() {
console.log("원본1222");
}
}
jest.mock("./mockClass");
jest.mock("./mockFunc");
import func from "./mockFunc";
import c from "./mockClass";
test("func, c 정의되어 있어야한다.", () => {
console.log(func, c);
expect(func).toBeDefined();
expect(c).toBeDefined();
});
테스트간 간섭 끊기 jest.resetModules, test.only
jest.resetModules
자바스크립트 import 모듈하면
페이지에서 다시 import 해서 사용할 때 캐시된 데이터를 보여준다.
위에서 import해서 추가한 prop을 아래 import 구문에서 확인할 수 있다.
test("reset modules", async () => {
const c = await import("./mockClass");
(c as any).prop = "hello";
expect(c).toBeDefined();
});
test("reset modules2222", async () => {
const c = await import("./mockClass");
expect((c as any).prop).toBe("hello");
});
테스트 실행전에 module를 reset 한다.
beforeEach(() => {
jest.resetModules();
});
파일 내 다른 테스트가 영향을 주는게 아닌
테스트는 개별적으로 수행될 수 있도록 한다.
test.only
only를 사용해 테스트가 영향을 받고있는지 확인과 개별적으로 수행할 수 있다.
test.only("reset modules2222", async () => {
const c = await import("./mockClass");
expect((c as any).prop).toBe("hello");
});
테스트 중복 줄이기
test.each
반복되는 코드를 테스트하는 경우
test.each([
[1, 1, 2],
[2, 3, 5],
[3, 4, 7],
])("%i 더하기 %i 는 %i", (a, b, c) => {
expect(a + b).toBe(c);
});
인수를 object로도 사용할 수있다.
test.each([
{ a: 1, b: 1, c: 2 },
{ a: 2, b: 3, c: 5 },
])("%a 더하기 %b 는 %c", ({ a, b, c }) => {
expect(a + b).toBe(c);
});
유사한 값도 통과시키기 test.any, test.closeTo
참고 https://jestjs.io/docs/expect
expect.anything()
null, undefined 만 아니면 된다.
expect.any()
Math.random을 사용해서 숫자를 받는 경우
expect.closeTo()
소수점 숫자를 연산하는경우 자주 사용
test('compare float in object properties', () => {
expect({
title: '0.1 + 0.2',
sum: 0.1 + 0.2,
}).toEqual({
title: '0.1 + 0.2',
sum: expect.closeTo(0.3, 5), //소수점 5자리까지 비교한다.
});
});
expect.stringContaining(string)
해당문자열이 있는지 확인하는 메소드
알아두면 유용한 실행 옵션 runInBand, watch, detectOpenHandles
runInBand
테스트 코드는 멀티스레드로 돌아간다.
내 환경이 cpu가 느리다면 runInBand를 사용해 싱글스레드로 사용할 수있다.
모노레포처럼 한 저장소에 여러가지 프로젝트가 있는경우 유용
참고
https://jestjs.io/docs/cli#--runinband
--maxWorkers=<num>|<string>
Cpu 몇개 사용할지 정할 수있다.
watch
테스트를 수정하면 수정한 코드를 테스트 돌려준다.
watchAll
테스트를 수정하면 모든 코드를 테스트 돌려준다.
수정한 테스트가 다른파일에 영향을 갈지도 모르니 watchAll을 통해 검증한다.
detectOpenHandles
예를 들어 테스트할때 db에 연결을 해두었는데 테스트 끝나고 db를 끊지 않았던 경우 leaking(누수)가 발생할 수있다.
누수를 감시할 수있는 detectOpenHandles
타이머경우 afterAll로 clearAllTimers()를 해주고
db는 db.close
detectOpenHandles로 누수를 확인하고 강제로 끌 수도 있다. --forceExit