![[Jest] Jest 공부 내용 정리](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdna%2Fwtx5W%2FbtsPIFsXNfd%2FAAAAAAAAAAAAAAAAAAAAAIwBvuT4bw11DcrGSkk6JL1raJZwi5msjT801ynAgw-i%2Fimg.png%3Fcredential%3DyqXZFxpELC7KVnFOS48ylbz2pIh7yKj8%26expires%3D1756652399%26allow_ip%3D%26allow_referer%3D%26signature%3DLEXKJJc5hpapHIdrW52CsrEa0RU%253D)
Jest 유용한 Matcher
Jest는 다양한 방식으로 테스트 할 수 있는 matcher를 지원합니다.
쉽게 설명하면 너가 의도하는 값이 이게 맞아? 라고 물어보는 메서드라고 보면 됩니다.
한국어로 모든 Matcher에 대한 자세한 설명을 보고 싶다면 아래 사이트를 참고하시면 됩니다!
https://runebook.dev/ko/docs/jest/expect
Jest Expect 한국어
Articles AWS Documentation Contributors GitHub
runebook.dev
Jest 공부 내용 정리
toBe()
- 단순 값 비교
// 함수
export const sum = (a: number, b: number) => {
return a + b;
};
// 테스트
test("sum은 두 숫자를 더해줍니다.", () => {
expect(sum(1, 2)).toBe(3);
});
// 비슷한 matcher => toBe, toEqual
// 차이점 값이 정확하게 일치하는지 비교하려면 toBe, 객체 비교하려면 toEqual
test("sum은 두 숫자를 더해줍니다.", () => {
// expect 여러개 사용 가능한데 전부다 true여야 테스트 통과
expect(sum(1, 2)).toBe(3);
expect(sum(1, 2)).toBe(3);
expect(sum(1, 2)).toBe(3);
});
// .not 붙이면 반대로 테스트 가능
test("sum은 두 숫자를 더해줍니다.", () => {
expect(sum(1, 2)).not.toBe(2);
});
비슷한 Matcher
// toBeNull - null 값만 허용
// toBeUndefined - undefined 값만 허용
// toBeDefined - undefined가 아닌 값
// toBeTruthy - true 값만 허용
// toBeFalsy - false 값만 허용 (false, 0, -0, 0n, "", null, undefined, NaN)
toEqual
- 객체 일치 검증
- toStrictEqual 사용하면 더 엄격
// 함수
export function getUser(id) {
return {
id,
email: `user${id}@test.com`,
};
}
// 테스트
test("return a user object", () => {
// getUser(1)의 리턴 결과값이 { 객체 } 값이 같은 경우 true
expect(getUser(1)).toEqual({
id: 1,
email: `user1@test.com`,
});
});
toMatchObject
- 클래스 객체 비교할 때 사용
toHaveBeenCalled
- 원래 toBeCalled 였는데 이름 바꿨다고 함
- 함수가 호출되었는지 여부 확인
- toHaveBeenCalled는 잘 사용안함 왜냐하면 함수가 호출되었냐보다 몇 번 호출되었는지, 인수는 뭘 전달햇는지가 더 중요함
그래서 toHaveBeenCalledTimes, toHaveBeenCalledWith을 더 많이 사용 한다고 함
// 함수
export const toHaveBeenCalled = (a: number, b: number) => {
return a + b;
};
export const obj = {
minus: (a: number, b: number) => {
return a - b;
},
};
// 테스트
test("toHaveBeenCalled은 함수가 호출됐는지 안됐는지 테스트 가능", () => {
// jest는 실제로 toHaveBeenCalled 함수가 호출되었는지 알지 못함
const toHaveBeenCalledSpy = jest.fn(toHaveBeenCalled); // mocking
toHaveBeenCalledSpy(1, 2); // toHaveBeenCalledSpy는 toHaveBeenCalled 함수를 호출하면서 호출 되었는지까지 검사해줌
expect(toHaveBeenCalledSpy).toHaveBeenCalled(); // 하지만 실제로 toHaveBeenCalled() 함수는 테스트 할 때 쓸모가 없는 경우가 많음 그래서 대체로 사용하는 것은
});
// 내가 몇 번 호출 되었는지
test("함수가 1번 호출되었는지 확인", () => {
const spy = jest.fn(toHaveBeenCalled);
spy(1, 2);
// 함수가 호출되었냐보다 몇 번 호출되었는지가 더 중요함 => toHaveBeenCalledTimes (얘가 더 상위호환임)
expect(spy).toHaveBeenCalledTimes(1);
});
// 내가 누구랑 호출 되었는지
test("함수가 1,2 와 함께 호출되었는지 확인", () => {
const spy = jest.fn(toHaveBeenCalled);
spy(1, 2);
// toHaveBeenCalledWith은 인수까지 확인해줌
// toHaveBeenCalled함수가 뭐랑 뭐랑 같이 호출됐는지 확인해줌 => 함수가 제대로된 인수를 받아서 호출됐는지 확인해줌
expect(spy).toHaveBeenCalledWith(1, 2);
});
// obj의 minus 메서드가 호출되었는지 테스트
test("obj의 minus 메서드 테스트 (mocking)", () => {
// obj.minus 함수를 복사해서 새로운 mock 함수 생성
const spy = jest.fn(obj.minus);
spy(1, 2); // 새로 만든 mock 함수 호출
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(1, 2);
});
test("obj의 minus 메서드 테스트 (spy)", () => {
// obj 객체의 minus 함수에 spy를 붙여서 테스트 하겠다는 뜻
const spy = jest.spyOn(obj, "minus"); // 감시할 객체 obj, 감시할 메서드 이름 minus
const result = obj.minus(1, 2); // 실제 객체의 메서드 호출해서 spy가 그 호출을 감지하고 기록함
expect(spy).toHaveBeenCalledWith(1, 2); // spy가 감시한 함수가 정확히 1, 2 인수로 호출되었는지 확인
expect(spy).toHaveBeenCalledTimes(1);
expect(result).toBe(-1);
});
// 정리
// fn - 가짜 함수 생성, 결과는 항상 undefined, mockReturnValue 사용해서 리턴 값 설정 가능
// spyOn - 객체의 함수에 spy를 붙여서 테스트
toHaveLength() / toContain()
- toHaveLength() 배열의 길이를 체크
- toContain() 특정 원소가 배열에 들어있는지 체크
test("array", () => {
const colors = ["Red", "Yellow", "Blue"];
expect(colors).toHaveLength(3); // 배열길이 3
expect(colors).toContain("Yellow"); // Yellow 문자열 원소를 가지고 있는지
expect(colors).not.toContain("Green"); // Green 문자열 원소를 안가지고 있는지
});
toThrow()
- 에러 발생 여부 테스트
// 함수
export function error() {
throw new Error();
}
// 에러를 extends한 class
export class CustomError extends Error {}
export function customError() {
throw new CustomError();
}
// 테스트
// toThrow(): 에러가 발생하는지만 확인
// toStrictEqual(): 에러 객체의 정확한 내용까지 확인
test("error 테스트", () => {
// toThrow로 비교하기도 전에 error() 함수에서 에러가 나버려서 테스트 실패함 (error() 함수가 즉시 실행됨)
// expect(error()).toThrow(Error);
// 이럴때는 error 함수를 함수로 감싸주면 테스트 통과함
// 어떤 함수가 에러를 throw 한다면 바로 넣지말고 감싸서 넣어줘야함
expect(() => error()).toThrow(Error);
expect(() => customError()).toThrow(CustomError);
});
test("error가 잘 난다(try/catch)", () => {
try {
error();
} catch (err) {
// toStrictEqual은 객체 비교할때 사용하는 함수 (에러 객체가 정확히 일치하는지 확인)
expect(err).toStrictEqual(new Error());
}
});
Function Mocking
- mockImplementation은 내가 원하는 함수로 덮을 수 있음
- mockReturnValue는 내가 원하는 반환값을 나오게 할 수 있음
// ++ spy를 한 번 심어놓으면 계속 유지됨 -> 다음 테스트 할 때 없애줘야함
// 없애는 방법 3가지
// 1. spy이 심은 함수 변수로 받아서 마지막에 spyFn.mockClear() 시켜주면됨
// 이외에도 mockReset, mockRestore가 있음
// mockClear - 스파이 함수가 몇 번 실행되었는지 누구와 함께 실행되었는지만 초기화 함 (Times, With 초기화)
// mockReset - mockImplementation과 같은거로 다시 정의한 함수 초기화 (mockClear + mockImplementation)
// mockRestore - 아예 원래 함수로 복구함
// 하지만 이거를 전부 test 함수마다 마지막에 중복으로 작성하면 코드가 지저분함
// 해결방법
// beforeAll - 이 파일의 준비사항 실행 ex.DB연결
// beforeEach - 각 테스트 전에 실행됨 ex.변수 초기화
// afterAll - 모든 테스트가 끝난후 실행됨 ex.DB연결 해제
// afterEach - 각 테스트 후에 실행됨 ex.mockRestore같은 공통된거 실행
// jest.clearAllMocks() - jest 자체의 모든 스파이 함수 초기화
// jest.resetAllMocks() - jest 자체의 모든 스파이 함수 초기화 + 원래 함수로 복구
// jest.restoreAllMocks() - jest 자체의 모든 스파이를 없애버림
// 위의 해결방법들을 모두에게 적용안하고 싶을때 구역을 나눌 수 있음 - describe 사용 or 아얘 파일 나눠서 사용
// 함수
export const obj = {
minus(x: number, y: number) {
return x - y;
},
};
// 테스트
// 예를들어 함수가 데이터베이스를 호출하는 함수라고 가정했을때
// 나는 해당 함수를 실제로 호출되었는지만 확인하고 싶고 데이터 베이스 요청은 실행시키지 않고 싶을 수 있음 (spyOn 사용)
let spyFn;
let beforeEachCount = 0;
let afterEachCount = 0;
// 바깥에 있는 beforeEach, afterEach는 모든 테스트에 적용됨
beforeEach(() => console.log("outside beforeEach", ++beforeEachCount));
afterEach(() => {
console.log("outside afterEach", ++afterEachCount);
jest.restoreAllMocks();
});
describe("beforeEach/afterEach 적용", () => {
// descibe 안에있는 beforeEach, afterEach는 해당 구역에만 적용됨
beforeEach(() => console.log("beforeEach", ++beforeEachCount));
afterEach(() => {
console.log("afterEach", ++afterEachCount);
jest.restoreAllMocks();
});
test("obj 객체의 함수 테스트 (spyOn)", () => {
jest.spyOn(obj, "minus");
const result = obj.minus(1, 2); // spy로 감시하고 있기에 obj.minus 실제 함수를 실행하면 spy가 감지함
expect(obj.minus).toHaveBeenCalledWith(1, 2);
expect(obj.minus).toHaveBeenCalledTimes(1);
expect(result).toBe(-1);
});
test("obj 객체의 함수에 스파이를 심긴 하되 실행은 하지 않고 싶을 때 (undefined 반환)", () => {
// mockImplementation은 해당 함수를 덮어씌워서 실행하지 않게 해줌 결과값은 undefined 반환함 (undefined 반환 함수 기본값)
jest.spyOn(obj, "minus").mockImplementation();
const result = obj.minus(1, 2); // obj.minus(1,2) 실행하면 undefined 반환함
expect(obj.minus).toHaveBeenCalledWith(1, 2);
expect(obj.minus).toHaveBeenCalledTimes(1);
expect(result).toBeUndefined(); // result = undefined
expect(result).not.toBe(-1);
});
// spyOn을 사용하면 기존 함수를 바꿔버릴 수 잇음
test("obj 객체의 함수에 스파이를 심고 리턴값을 바꾸게", () => {
jest.spyOn(obj, "minus").mockImplementation((a, b) => a + b);
const result = obj.minus(1, 2); // obj.minus 함수는 덧셈함수로 덮어씌워짐
expect(obj.minus).toHaveBeenCalledWith(1, 2);
expect(obj.minus).toHaveBeenCalledTimes(1);
expect(result).toBe(3);
});
test("obj.minus에 스파이를 심고 리턴값이 서로 다르게 나오게", () => {
jest
.spyOn(obj, "minus")
.mockImplementationOnce((a, b) => a + b) // 처음은 덧셈함수
.mockImplementationOnce(() => 5) // 두번째는 5리턴함수
.mockImplementation((a, b) => a + b); // 세번째부터는 덧셈함수
const result1 = obj.minus(1, 2);
const result2 = obj.minus(1, 2);
const result3 = obj.minus(1, 2);
expect(obj.minus).toHaveBeenCalledTimes(3);
expect(result1).toBe(3);
expect(result2).toBe(5);
expect(result3).toBe(3);
});
});
describe("beforeAll/afterAll 적용", () => {
afterAll(() => {
jest.restoreAllMocks();
});
test("함수를 바꾸지 않고 리턴값만 바꾸고 싶을때", () => {
jest.spyOn(obj, "minus").mockReturnValue(100);
const result = obj.minus(1, 2);
expect(obj.minus).toHaveBeenCalledTimes(1);
expect(result).toBe(100);
});
test("함수를 바꾸지 않고 리턴값만 바꾸고 싶을때 (mockReturnValueOnce)", () => {
jest
.spyOn(obj, "minus")
.mockReturnValueOnce(100) // 이것도 똑같이 이어 적을 수 있음
.mockReturnValueOnce(-1)
.mockReturnValue(3);
const result1 = obj.minus(1, 2);
const result2 = obj.minus(1, 2);
const result3 = obj.minus(1, 2);
expect(obj.minus).toHaveBeenCalledTimes(3);
expect(result1).toBe(100);
expect(result2).toBe(-1);
expect(result3).toBe(3);
});
});
beforeAll(() => console.log("이 파일의 준비사항 실행"));
afterAll(() => console.log("모든 테스트가 끝난 후 실행"));
Async Function
주의! - 프로미스 테스트 할때는 앞에 꼭 return 추가해주자
성공은 resolves, then
실패는 rejects, catch
// 함수
export function okPromise() {
return Promise.resolve("ok");
}
export function noPromise() {
return Promise.reject("no");
}
export async function okAsync() {
return "ok";
}
export async function noAsync() {
throw "no";
}
// 테스트
import * as fns from "./asyncFunction";
// 비동기 함수 테스트
test("okPromise 테스트", () => {
expect(fns.okPromise()).resolves.toBe("ok"); // 함수가 promise를 리턴하면 꼭 resolves 넣어야함
});
test("okPromise 테스트 2", () => {
// mocking해서 promise 함수 테스트 할 수 있는데 함정이 있음 no를 해도 테스트 통과함 - 강의에서는 통과로 나오는데 나는 실패로 나옴
// 통과로 나올때 해결법은 okSpy가 프로미스 함수고 resolves 사용할때는 앞에 return 추가해줘야됨
const okSpy = jest.fn(fns.okPromise);
// 이렇게 return을 추가해줘야 promise가 resolve될 때 까지 기다리고 나서 테스트 성공 유무 판단
// return이 없으면 resolve 되기 전에 테스트가 끝나버림
return expect(okSpy()).resolves.toBe("ok");
});
test("okPromise 테스트 3", () => {
const okSpy = jest.fn(fns.okPromise);
// then을 사용하면 resolves 사용하지 않아도 됨
// result 자체는 promise가 아니라 그냥 문자열임
// 이것도 마찬가지로 return 추가해줘야 테스트가 성공할때까지 기다림
return okSpy().then((result) => expect(result).toBe("ok"));
});
test("okPromise 테스트 4", async () => {
const okSpy = jest.fn(fns.okPromise);
const result = await okSpy();
// async await은 return 필요 없음
expect(result).toBe("ok");
});
test("okPromise 테스트 5", () => {
// jest.spyOn(fns, "okPromise").mockReturnValue(Promise.resolve("ok"));
jest.spyOn(fns, "okPromise").mockResolvedValue("ok");
return expect(fns.okPromise()).resolves.toBe("ok");
});
test("noPromise 테스트", () => {
const noSpy = jest.fn(fns.noPromise);
// throw 되는 경우는 catch 사용해야함
return noSpy().catch((result) => expect(result).toBe("no"));
});
test("noPromise 테스트 2", () => {
const noSpy = jest.fn(fns.noPromise);
return expect(noSpy()).rejects.toBe("no");
});
test("noPromise 테스트 3", async () => {
const noSpy = jest.fn(fns.noPromise);
try {
await noSpy();
} catch (error) {
expect(error).toBe("no");
}
});
test("noPromise 테스트 4", () => {
// jest.spyOn(fns, "noPromise").mockReturnValue(Promise.reject("no"));
jest.spyOn(fns, "noPromise").mockRejectedValue("no");
return expect(fns.noPromise()).rejects.toBe("no");
});
Callback Function
- 콜백 함수를 테스트할때는 done 매개변수 사용해서 테스트 가능 (5초까지)
- 넘어갈 경우 advanceTimersByTime 시간 앞다기거나
- runAllTimers로 시간 앞당겨 테스트하기
// 함수
export function timer(callback) {
setTimeout(() => {
callback("success");
}, 3000);
}
export function timer2(callback) {
setTimeout(() => {
callback("success");
}, 10_000);
}
// 테스트
import { timer, timer2 } from "./callback";
test("timer 잘 실행되는지 테스트", (done) => {
timer((msg: string) => {
// 이것도 "success"가 아닌 문자도 통과함
// jest가 비동기 3초를 못기다림
// 이거를 기다리게 하려면 done이라는 함수 매개변수를 받아서 내가 테스트를 멈추고 싶을때 넣어주면 타이머 시간을 기다림
expect(msg).toBe("success");
done(); // 비동기 작업이 끝났다고 알려주는 함수
});
});
// timer2는 10초 걸리는 함수인데 done은 5초 안에 끝나는 비동기 함수만 테스트 가능함
test("시간아 빨리가라!", (done) => {
expect.assertions(1); // 1번 실행되어야 테스트 성공 - 특히 비동기 함수 테스트할때는 조금만 실수해도 성공해버리는 경우가 잇어서 이렇게 확신이 들게 하는것도 테스트에서 중요함
jest.useFakeTimers();
timer2((msg: string) => {
// expect를 없애도 테스트 성공으로 나오는데 이럴때는 expect 실행된 횟수를 테스트 해야함 - expect.assertions
expect(msg).toBe("success");
done();
});
// jest.runAllTimers(); // 시간을 빠르게 감아서 5초 안에 끝나도록함
// runAllTimers 말고도 시간 수동으로 앞당길수도있음
jest.advanceTimersByTime(10_000); // 10초 앞당김
});
// jest.runAllTicks()
// 바로 promise, async/await 반환할 수 있는 메소드
// jest.runAllTimers()
// 바로 setTimeout, setInterval의 콜백 함수를 실행할 수 있게 하는 메소드 + runAllTicks
// jest.runAllImmediates()
// 바로 setImmediate 콜백 함수를 실행할 수 있게 하는 메소드
// jest.advanceTimersByTime
// 타이머를 특정 시간 만큼만 진행시킴
// jest.clearAllTimers
// 타이머 없애는 메소드, runAllTimers로 타이머 실행 시켜도 실행되지 않음
// jest.getTimersCount()
// 타이머 몇개인지 카운트 하는 메소드
Date Object
- jest fakeTimer 기능이 많다.
- https://jestjs.io/docs/jest-object#jestusefaketimersfaketimersconfig
The Jest Object · Jest
The jest object is automatically in scope within every test file. The methods in the jest object help create mocks and let you control Jest's overall behavior. It can also be imported explicitly by via import from '@jest/globals'.
jestjs.io
// 함수
// 3일 뒤를 반환하는 함수
export function after3days() {
const date = new Date();
date.setDate(date.getDate() + 3);
return date;
}
// 테스트
import { after3days } from "./date";
// 아래처럼 생각할 수 있는데 진짜 1000분의 1초 차이로 테스트 실패함
// test("3일 후를 리턴한다.", () => {
// const date = new Date();
// date.setDate(date.getDate() + 3);
// expect(after3days()).toStrictEqual(date);
// });
// 이럴때는 가짜 타이머를 적용해서 테스트 할 수 있음
test("3일 후를 리턴한다.", () => {
jest.useFakeTimers().setSystemTime(new Date(2025, 8, 7)); // 오늘이 9월 7일이라고 가정 (월은 0부터 시작하므로 8 = 9월)
console.log(new Date()); // 2025-09-06T15:00:00.000Z - Z가 붙으면 런던시간으로 +9시간을 더해줘야 한국시간임
expect(after3days()).toStrictEqual(new Date(2025, 8, 10)); // 3일 뒤인 9월 10일이 나와야함
});
// useFakkeTimer 사용했으면 다시 원래대로 돌려줘야함
afterEach(() => jest.useRealTimers());
+ 추가
// jest.runAllTicks()
// 바로 promise, async/await 반환할 수 있는 메소드
// jest.runAllTimers()
// 바로 setTimeout, setInterval의 콜백 함수를 실행할 수 있게 하는 메소드 + runAllTicks
// jest.runAllImmediates()
// 바로 setImmediate 콜백 함수를 실행할 수 있게 하는 메소드
// jest.advanceTimersByTime
// 타이머를 특정 시간 만큼만 진행시킴
// jest.clearAllTimers
// 타이머 없애는 메소드, runAllTimers로 타이머 실행 시켜도 실행되지 않음
// jest.getTimersCount()
// 타이머 몇개인지 카운트 하는 메소드
// jest.getRealSystemTime()
// useFakeTimer를 사용해서 가짜시간을 설정하면 실제시간을 볼 수 있는방법은 jest.getRealSystemTime 밖에 없다.
toBeCloseTo
- 부동 소수점 오차 무시 계산 테스트
- JavaScript는 0.1 + 0.2 = 0.3이 나올수 없다.
test("0.1 + 0.2 는 0.3", () => {
expect(0.1 + 0.2).toBeCloseTo(0.3);
});
Function 호출 순서 테스트 (mock.calls + Jest-extended)
- 함수가 호출되었는지, 인수로 뭐가 전달되었는지 보다 함수가 호출된 순서를 아는게 중요한 경우도 있습니다.
+) 첫번째 테스트는 Jest-extended 확장 패키지 없이 작성했고 두번째 테스트는 Jest-extended 설치해서 작성한 코드입니다.
설치방법
https://edongdong.tistory.com/427
[Jest] Jest 확장 프로그램 jest-extended 사용하기
Jest 확장 프로그램 jest-extended 사용하기 Jest-extended란?- jest-extended는 Jest에서 사용할 수 있는 추가적인 matcher들을 제공하는 확장 라이브러리입니다.- Jest에서 기본적으로 제공하는 matcher(toBe, toEqual,
edongdong.tistory.com
// 함수
export function first() {}
export function second() {}
export function third() {}
// 테스트
// mock 속성에는 함수가 호출되었을때의 함수 정보가 객체로 들어있음
// mock.calls에는 함수가 호출된 인수 정보가 배열로 들어있음
// 즉, 앞에서 배운 toHaveBeenCalledWith이 mock.calls에 있는 인수와 일치하는지 확인하는 함수인거임
// toHaveBeenCalledTimes는 mock.calls 배열의 length를 검사하는거임
// mock.invocationCallOrder는 함수가 호출된 순서를 배열로 반환함 (하나의 함수가 여러번 호출 됐을 수 있어서 배열 형태)
test("first->second->third", () => {
// spy 함수 생성
const spy1 = jest.fn(first);
const spy2 = jest.fn(second);
const spy3 = jest.fn(third);
// 함수 호출
(spy1 as any)(1, 2, 3);
spy2();
(spy1 as any)("hello");
spy3();
spy1();
// toBeLessThan - expect 함수의 호출순서가 toBeLessThan 함수의 호출순서보다 작은지 확인
// toBeGreaterThan - expect 함수의 호출순서가 toBeGreaterThan 함수의 호출순서보다 큰지 확인
// 아래오 같이 작성하면 처음 내 코드를 보는 사람은 이해하기 어려움
// 이럴때 jest-extended 사용하면 더 가독성있게 작성할 수 있음
expect(spy1.mock.invocationCallOrder[0]).toBeLessThan(
spy2.mock.invocationCallOrder[0]
);
expect(spy3.mock.invocationCallOrder[0]).toBeGreaterThan(
spy2.mock.invocationCallOrder[0]
);
});
test.skip("first->second->third 2", () => {
const spy1 = jest.fn(first);
const spy2 = jest.fn(second);
const spy3 = jest.fn(third);
(spy1 as any)(1, 2, 3);
spy2();
(spy1 as any)("hello");
spy3();
spy1();
// 함수 호출 순서 확인 (jest-extended 사용)
expect(spy1).toHaveBeenCalledBefore(spy2);
expect(spy3).toHaveBeenCalledAfter(spy2);
});
test("인수의 일부 테스트", () => {
// 가짜 함수 생성
const fn = jest.fn();
// 가짜 함수에 인수를 넣어서 호출하면 mock.calls에 인수가 들어감 - 인수 일부 테스트 가능
fn({
a: {
b: {
c: "hello",
},
d: "bye",
},
e: ["f"],
});
expect(fn.mock.calls[0][0].a.b.c).toBe("hello");
});
Module 테스트 (jest.mock())
- 하나씩 spy를 심기 귀찮거나 모듈 전체를 모킹하고싶을 경우
- requireActual은 모킹 하기 싫은 부분만 spy 안심게함
- replaceProperty는 객체의 속성 값 바꾸고 싶을때 사용
// 함수
export const obj = {
method() {
return "123";
},
method2() {
return "123";
},
method3() {
return "123";
},
method4() {
return "123";
},
method5() {
return "123";
},
prop: "change me",
};
// 테스트
// 모듈 전체를 모킹 (메서드 전부에 spyOn 해준것과 마찬가지, prop은 모킹되지 않음)
// 만약 덮어씌우고 싶다면 두번째 인자로 콜백 함수 넣어서 덮어씌우기 가능함 (조심! 전부 다 덮어씌워짐)
// 만약에 메서드 하나만 모킹하고 싶다면
// 1. 그 메서드만 spyOn 해주기
// 2. jest.requireActual 사용해서 원본 모듈 spread 해주기
// 주의! - jest.mock은 호이스팅 되기 때문에 test 내부에 작성해도 최상단으로 올라감
// 호이스팅 동작이 마음에 안든다면 spyOn 해주기
// jest.mock("./module", () => {
// return {
// ...jest.requireActual("./module"),
// obj: {
// ...jest.requireActual("./module").obj,
// method3: jest.fn(),
// },
// };
// });
jest.mock("./module");
// 위에 jest.mock 코드 보면 모킹에 대한 구현을 지금 파일에서 하고 있는데 이렇게 안하고도 가능함
// 테스트 하고자하는 함수가 있는 파일과 같은 경로상에 __mocks__ 폴더를 만들고 그 안에 이름이 똑같은 테스트 하고자하는 함수 파일을 만들어서 모킹 코드를 작성해주면 됨
// 그럼 그 파일에 있는 모킹 코드가 테스트 파일에서 자동으로 불러와짐
// 보통 __mocks__ 폴더는 모든 테스트에 공통적으로 모킹을 해야 되는 경우에 주로 사용함, 개별적으로 모킹 해야한다면 위와 같이 작성함
jest.mock("axios");
test("모듈을 전부 모킹", () => {
// 이렇게 하나씩 메서드 모킹하는 것은 비효율적
// jest.spyOn(obj, "method");
// jest.spyOn(obj, "method2");
// jest.spyOn(obj, "method3");
// jest.spyOn(obj, "method4");
// jest.spyOn(obj, "method5");
// obj의 prop도 교체해주고 싶을때는
jest.replaceProperty(obj, "prop", "hello");
// console.log(obj); // 모킹된 객체 출력
});
test("axios 모킹", () => {
const mockedAxios = jest.mocked(axios);
// console.log(mockedAxios); // 함수들은 모두 모킹되어있는데 속성들은 모킹되지 않음
});
// 정리
// 함수나 메서드 수정 - spyOn
// 속성 수정 - replaceProperty
// __mocks__/module.ts
export const obj = {
method: jest.fn(),
method2() {
return "123";
},
method3() {
return "123";
},
method4() {
return "123";
},
method5() {
return "123";
},
prop: "change me",
};
테스트간 간섭 끊기 (jest.resetModules)
- 만약에 테스트 간 모듈 캐시를 초기화 하고 싶다면 jest.resetModules() 사용하면 모듈 캐시 날려줄 수 있습니다.
// beforeEach(() => {
// jest.resetModules();
// });
test("first import", async () => {
// module을 import 함수 사용해서 가져올 수 있음 - dynamic import
const c = await import("./mockClass");
(c as any).prop = "hello"; // 임의로 속성 추가
expect(c).toBeDefined();
});
test("second import", async () => {
// 임의로 속성 추가한 상태에서 다시 mockClass 가져오면 속성 유지됨
// JavaScript 에서는 모듈을 가져오면 캐시되어있음
const c = await import("./mockClass");
expect((c as any).prop).toBe("hello");
});
테스트 중복 줄이기 (test.each / it.each)
// test("1 더하기 2는 3", () => {
// expect(1 + 2).toBe(3);
// });
// test("2 더하기 3는 5", () => {
// expect(2 + 3).toBe(5);
// });
// test("3 더하기 4는 7", () => {
// expect(3 + 4).toBe(7);
// });
// 비슷한 패턴의 테스트를 할 때는 test.each 사용 - 고차함수
test.each([
[1, 2, 3],
[2, 3, 5],
[3, 4, 7],
])("%i 더하기 %i는 %i", (a, b, c) => {
expect(a + b).toBe(c);
});
// or 순서를 안지키고 싶다면
test.each([
{ a: 1, b: 2, c: 3 },
{ a: 2, b: 3, c: 5 },
{ a: 3, b: 4, c: 7 },
])("%i 더하기 %i는 %i", ({ a, b, c }) => {
expect(a + b).toBe(c);
});
유사한 값 통과시키기
- expect.anything() - null, undefined만 아닌 모든값을 허용
- 다른 방법으로는 not.toBeNull, not.toBeUndefined 가 있는데 하나로 퉁칠 수 있다.
- expect.any() - 주로 Math.random() 사용할 때 사용함(테스트 할때마다 값이 달라지기 때문)
- Math.random 사용한다면 expect.any(Number) - 해당 타입에 맞게 String, Object ....
- 속성 값에도 적용가능함
- 또는 Math.random()을 사용하는 값에 spyOn으로 고정값으로 맞추는 방법도 있음
- expect.closeTo() - 앞에서 설명했음
- 자바스크립트에서는 0.1 + 0.2 === 0.3은 false로 나옴 - 보동소수점 문제 해결해줌
- expect.stringContaining - 문자열 안에 문자가 들어있는지 확인
- expect.stringMatching - 정규표현식 검사
알아두면 좋은 유용한 실행 옵션
테스트를 할때마다 npm test or npx jest을 호출했어야 했는데 명령어 뒤에 붙일 수 있는 옵션들이 많은데 그중에서 꼭 알아두면 좋은 옵션들을 소개하려고 합니다
- --detectOpenHandles
- test할 함수에 setInterval이 있어서 무한히 실행되는 현상이 있거나
- 무언가를 서버에 요청하는데 무한히 요청되는 경우
- 이런 현상을 감지해서 찾아서 종료할 수 있게 도와줌
- 또는 테스트 코드 처음에 beforeEach로 jest.useFakeTimers, afterEach에 jest.clearAllTimers를 해서 모든 테스트에 적용 시키는 방법도 있음
- --runInBand - 테스트를 싱글 스레드에서 돌아가게 함
- Jest는 기본적으로 테스트를 멀티 스레드에서 돌아가게 함
- 그럼 멀티 스레드가 더 좋은거 아니냐고 생각할수 있는데 상황마다 다름
- 모노레포 프로젝트 같은 경우에 모든 프로젝트를 테스트를 할 경우 스레드가 너무 많이 생성돼서 오히려 싱글 스레드로 돌리는게 성능이 더 좋은 경우도 있다고 함
- --watch
- --watch는 내가 방금 수정한 코드만 자동으로 바로바로 테스트 실행
- --watchAll은 내가 방금 수정한 코드 뿐만 아니라 모든 테스트 자동 테스트 실행
(내가 방금 수정한 테스트가 다른 파일에 영향을 끼치는지 확인하고 싶다면)
틀린 부분이 있다면 피드백 편하게 해주세요~
프론트엔드 공부일지 입니다.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!