npm init
제스트 설치
npm install jest -D
//package.json
// test를 jest로 바꾸기
{
"name": "jest_tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^29.7.0"
}
}
// fn.js
const fn = {
add: (num1, num2) => num1 + num2,
};
module.exports = fn;
// fn.test.js
const fn = require("./fn");
test("1은 1이야", () => { // "1은 1이야"는 테스트 설명 작성
expect(1).toBe(1); // expect는 검증할 값, toBe는 기대되는 값
});
test("2더하기 3은 5야", () => {
expect(fn.add(2, 3)).toBe(5); // not.toBe로 하면 5가 아닌 값이 기대되는 값으로 됨
});
npm test로 테스트 코드 실행

expect()다음에 작성하는 부분을 Matchers라고 한다.
Matchers에는
toBe: 값이 정확하게 같은지 검사(===)
toEqual: 값이 동등한지 검사(==)
toStrictEqual: 엄격한 toEqual, undefined를 허용하지 않음
toBeNull: null이면 통과
toBeUndefined: undefined이면 통과
toBeDefined: 값이 정의되어 있는지 검사
toBeTruthy: true 통과
toBeFalsy: false 통과
toBeGreaterThan: 크다
toBeGreaterThanOrEqual: 크거나 같다
toBeLessThan: 작다
toBeLessThanOrEqual: 작거나 같다
toBeCloseTo: 숫자에 가까이 있는지 확인
toMatch: 글자 포함 여부
toContain: 배열에 특정 요소가 있는지 확인
toThrow: (특정) 에러가 나는지 확인
// fn.js
const fn = {
add: (num1, num2) => num1 + num2,
makeUser: (name, age) => ({ name, age, gender: undefined }),
throwErr: () => {
throw new Error("XX");
},
};
module.exports = fn;
// fn.test.js
test("이름과 나이를 전달받아서 객체를 반환해줘", () => {
expect(fn.makeUser("Mike", 30)).toBe({
name: "Mike",
age: 30,
});
}); // 테스트 실패함
test("이름과 나이를 전달받아서 객체를 반환해줘", () => {
expect(fn.makeUser("Mike", 30)).toEqual({
name: "Mike",
age: 30,
});
}); // 테스트 성공함
test("이름과 나이를 전달받아서 객체를 반환해줘", () => {
expect(fn.makeUser("Mike", 30)).toStrictEqual({
name: "Mike",
age: 30,
});
}); // 테스트 실패함
test("null은 null입니다", () => {
expect(null).toBeNull(); // 성공
});
test("0은 false 입니다.", () => {
expect(fn.add(1, -1)).toBeFalsy();
}); // 성공
test("ID는 10자 이하로 제한", () => {
const id = "THE_BLACK_ORDER";
expect(id.length).toBeLessThanOrEqual(10);
}); // 실패
// 소수점 일치
test("0.1 더하기 0.2는 0.3입니다", () => {
const id = "THE_BLACK_ORDER";
expect(fn.add(0.1, 0.2)).toBe(0.3);
}); // 실패, 0.30000000000000004
// 자바스크립트는 소수를 2진법으로 바꿀 때 소수의 숫자가 무한소수점으로 바뀔 수 있음
test("0.1 더하기 0.2는 0.3입니다", () => {
const id = "THE_BLACK_ORDER";
expect(fn.add(0.1, 0.2)).toBeCloseTo(0.3);
}); // 성공. toBeCloseTo는 숫자에 가깝게 있는지 확인. 소수점 계산에 사용
test("H라는 글자 여부 확인", () => {
expect("Hello World").toMatch(/h/i);
}); // 성공, /h/i는 정규표현식, i는 대소문자 구분하지 않음
test("유저 리스트에 Mike가 있나?", () => {
const user = "Mike";
const userList = ["Tom", "Jane", "Kai"];
expect(userList).toContain(user);
});
test("이거 에러 나나요?", () => {
expect(() => fn.throwErr()).toThrow();
}); // 통과
test("이거 에러 나나요?", () => {
expect(() => fn.throwErr()).toThrow("00");
}); // 실패
test("이거 에러 나나요?", () => {
expect(() => fn.throwErr()).toThrow("XX");
}); // 통과
test함수에 콜백함수로 done을 사용한다. done()이 나오면 코드가 끝남. done이 없으면 비동기 코드가 정상적으로 테스트 되지 않는다.
promise 사용시에는 done을 사용하지 않아도 된다. 하지만 테스트 코드 함수에서 return을 넣아야 함
async/await에서는 사용하기 더 쉬움
// fn.js
const fn = {
add: (num1, num2) => num1 + num2,
getName: (callback) => {
const name = "Mike";
setTimeout(() => {
callback(name);
}, 3000);
},
getAge: () => {
const age = 30;
return new Promise((res, rej) => {
setTimeout(() => {
res(age);
// rej('error') // 에러상황
}, 3000);
});
},
};
module.exports = fn;
// fn.test.js
const fn = require("./fn");
test("3초 후에 받아온 이름은 Mike", (done) => {
function callback(name) {
expect(name).toBe("Mike");
done();
}
fn.getName(callback);
}); // 3초 후 통과 함
// api 테스트
test("3초 후에 받아온 이름은 Mike", (done) => {
function callback(name) {
try {
expect(name).toBe("Mike");
done();
} catch (error) {
done();
}
}
fn.getName(callback);
}); // 3초 후 통과 함
// promise return
test("3초 후에 받아온 나이는 30", () => {
return fn.getAge().then((age) => {
expect(age).toBe(30);
});
});
// resolves
test("3초 후에 받아온 나이는 30", () => {
return expect(fn.getAge()).resolves.toBe(30);
});
// rejects
test("3초 후에 에러가 납니다", () => {
return expect(fn.getAge()).rejects.toMatch("error");
});
// async, await
test("3초 후에 받아온 나이는 30", async () => {
const age = await fn.getAge();
expect(age).toBe(30);
});
// async, await 에서 resolves, rejects 사용
test("3초 후에 받아온 나이는 30", async () => {
await expect(fn.getAge()).resolves.toBe(30);
});
let num = 0;
test("0더하기 1은 1이야", () => {
num = fn.add(num, 1);
expect(num).toBe(1);
}); // 통과
test("0더하기 2은 2이야", () => {
num = fn.add(num, 2);
expect(num).toBe(2);
}); // 실패
test("0더하기 3은 3이야", () => {
num = fn.add(num, 3);
expect(num).toBe(3);
}); // 실패
test("0더하기 4은 4이야", () => {
num = fn.add(num, 4);
expect(num).toBe(4);
}); // 실패
num값이 누적이 되서 실패한다.
beforeEach, afterEach 를 사용해서 해결 할 수 있음
// 각각 테스트 전에 실행
beforeEach(() => {
num = 0;
});
// 각각 테스트 후에 실행
afterEach(() => {
num = 0;
});
const fn = {
add: (num1, num2) => num1 + num2,
connectUserDb: () => {
return new Promise((res, rej) => {
setTimeout(() => {
res({
name: "Mike",
age: 30,
gender: "male",
});
}, 500);
});
},
disconnectDb: () => {
return new Promise((res) => {
setTimeout(() => {
res();
}, 500);
});
},
connectCarDb: () => {
return new Promise((res, rej) => {
setTimeout(() => {
res({
brand: "bmw",
name: "z4",
color: "red",
});
}, 500);
});
},
disconnectCarDb: () => {
return new Promise((res) => {
setTimeout(() => {
res();
}, 500);
});
},
};
module.exports = fn;
// fn.test.js
const fn = require("./fn");
let user;
// 테스트 전 최초 1번
beforeAll(async () => {
user = await fn.connectUserDb();
});
// 테스트 후 최초 1번
afterAll(() => {
return fn.disconnectDb();
});
test("이름은 Mike", () => {
expect(user.name).toBe("Mike");
}); // 1초
test("나이는 30", () => {
expect(user.age).toBe(30);
}); // + 1초
test("성별은 남성", () => {
expect(user.gender).toBe("male");
}); // + 1초
// describe로 묶기, describe 내부 before와 after는 describe에서만 작동
describe("Car관련 작업", () => {
let car;
// 테스트 전 최초 1번
beforeAll(async () => {
car = await fn.connectCarDb();
});
// 테스트 후 최초 1번
afterAll(() => {
return fn.disconnectCarDb();
});
test("차 이름", () => {
expect(car.name).toBe("z4");
});
test("브랜드는 bmw", () => {
expect(car.brand).toBe("bmw");
});
test("색상은 red", () => {
expect(car.color).toBe("red");
});
}); // 1초
test.only는 해당 테스트 코드만 실행
test.skip은 해당 테스트 코드만 실행 제외
테스트 하기 위해 흉내만 내는 함수(데이터 베이스 등)
// 간단 목함수 설명
const mockFn = jest.fn(); //jest.fn()으로 mock 함수를 만들 수 있다.
mockFn();
mockFn(1);
test("mock 안에 calls라는 값을 확인하기 위한 테스트", () => {
console.log(mockFn.mock.calls);
expect("dd").toBe("dd");
});
// [ [], [ 1 ] ] 배열이 나옴
// .mock 프로퍼티에는 호출된 값들이 고스란히 저장되어 있음(몇 번 호출 되었는지, 어떤 인수를 전달받았는지 알 수 있음)
// .calls 로 알 수 있는 것은 몇 번 호출 되었는지, 어떤 인수를 전달받았는지 알 수 있음
test("함수는 2번 호출됩니다", () => {
expect(mockFn.mock.calls.length).toBe(2);
});
test("2번째로 호출된 함수에 전달된 첫번째 인수는 1 입니다", () => {
expect(mockFn.mock.calls[1][0]).toBe(1);
});

// 간단 테스트
const mockFn = jest.fn();
function forEachAdd1(arr) {
arr.forEach((num) => {
mockFn(num + 1);
});
}
forEachAdd1([10, 20, 30]);
test("함수 호출은 3번 됩니다.", () => {
expect(mockFn.mock.calls.length).toBe(3);
});
test("전달된 값은 11, 21, 31", () => {
expect(mockFn.mock.calls[0][0]).toBe(11);
expect(mockFn.mock.calls[1][0]).toBe(21);
expect(mockFn.mock.calls[2][0]).toBe(31);
});
// results값 호출해보기
const mockFn = jest.fn((num) => num + 1);
mockFn(10);
mockFn(20);
mockFn(30);
test("함수 호출은 3번 됩니다.", () => {
console.log(mockFn.mock.results);
expect(mockFn.mock.calls.length).toBe(3);
});
// results.value
test("10에서 1 증가한 값이 반환된다.", () => {
expect(mockFn.mock.results[0].value).toBe(11);
});
test("20에서 1 증가한 값이 반환된다.", () => {
expect(mockFn.mock.results[1].value).toBe(21);
});
test("30에서 1 증가한 값이 반환된다.", () => {
expect(mockFn.mock.results[2].value).toBe(31);
});

// mockReturnValueOnce는 실행할 때 마다 각각 다른 값을 return 해 주기
const mockFn = jest.fn();
mockFn
.mockReturnValueOnce(10)
.mockReturnValueOnce(20)
.mockReturnValueOnce(30)
.mockReturnValue(40);
mockFn();
mockFn();
mockFn();
mockFn();
test("mockReturnValue, mockReturnValueOnce 확인하기", () => {
console.log(mockFn.mock.results);
expect("dd").toBe("dd");
});

// mockResolvedValue로 비동기함수 흉내내기
mockFn.mockResolvedValue({ name: "Mike" });
test("받아온 이름은 Mike", () => {
mockFn().then((res) => {
expect(res.name).toBe("Mike");
});
});
특정 기능의 가짜 함수
//fn.js
const fn = {
createUser: (name) => {
console.log("실제로 사용자가 생성되었습니다.");
return { name: name };
},
}
module.exports = fn;
const fn = require("./fn");
// 일반적인 방법
test("유저를 만든다", () => {
const user = fn.createUser("Mike");
expect(user.name).toBe("Mike");
}); // "실제로 사용자가 생성되었습니다."가 표시된다.
jest.mock 방식
const fn = require("./fn");
// mock을 이용한 db 테스트 방법
jest.mock("./fn"); // jest.mock으로 fn을 mocking module로 만듬
// 해당 방식은 fn.createUser를 실제로 호출하지 않음
fn.createUser.mockReturnValue({ name: "Mike" }); // mocking함수가 동작을 함, 실제 유저가 생성되지는 않음
const mockFn = jest.fn();
mockFn(10, 20);
mockFn();
mockFn(30, 40);
test("한번 이상 호출?", () => {
expect(mockFn).toBeCalled();
});
test("정확히 세번 호출?", () => {
expect(mockFn).toBeCalledTimes(3);
});
test("10이랑 20 전달받은 함수가 있는가?", () => {
expect(mockFn).toBeCalledWith(10, 20);
});
test("마지막 함수는 30이랑 40 받았음?", () => {
expect(mockFn).lastCalledWith(30, 40);
});
간단한 컴포넌트 생성: Hello라는 컴포넌트를 만들고, props로 user를 받아서 user의 name이 있으면 환영 메시지를 보여주고, 없으면 로그인 버튼을 보여주는 간단한 UI를 구현합니다.
render와 screen 사용: 테스팅 라이브러리에서 render와 screen을 import하여 컴포넌트를 렌더링하고, 화면에 특정 텍스트가 있는지 확인하는 테스트 코드를 작성합니다.
스냅샷 테스트: Jest의 스냅샷 기능을 사용하여 컴포넌트의 렌더링 결과를 파일로 저장하고, 변경사항이 있을 때 비교하는 방식으로 테스트를 진행합니다. 스냅샷 테스트는 UI가 복잡하거나 자주 바뀌지 않는 경우에 유용합니다.
// Hello.js
import React from 'react';
export default function Hello({ user }) {
return (
<div>
<h1>Hello</h1>
{user?.name ? (
<p>Welcome, {user.name}</p>
) : (
<button>Login</button>
)}
</div>
);
}
// Hello.test.js
import { render, screen } from '@testing-library/react';
import Hello from './Hello';
const user = {
name: "Mike",
age: 30,
};
const user2 = {
age: 20,
};
test("snapshot: name 있음", () => {
const view = render(<Hello user={user} />);
expect(view).toMatchSnapshot();
});
test("snapshot: name 없음", () => {
const view = render(<Hello />);
expect(view).toMatchSnapshot();
});
test("Hello 라는 글자가 포함되는가?", () => {
render(<Hello user={user} />); // render를 통해 Hello컴포넌트 불러 옴
const helloEl = screen.getByText(/Hello/i); // screen에서 Hello텍스트가 있는지 확인
expect(helloEl).toBeInTheDocument(); // Document 안에 지정한 텍스트가 있는지 확인
});
테스트
mock 함수 사용: 시간에 따라 변하는 값이나 외부 API 호출과 같은 것들을 테스트할 때는 mock 함수를 사용하여 고정된 값으로 바꾸어 주어야 합니다. 예를 들어, 현재 시간의 초를 보여주는 Timer 컴포넌트를 테스트할 때는 Date.now 함수를 mock하여 테스트 코드에서 원하는 값을 반환하게 합니다.
// Timer.js
export default function Timer() {
const now = Date.now();
const sec = new Date(now).getSeconds();
return <p>현재 {sec}초 입니다.</p>;
}
// Timer.test.js
import { render } from "@testing-library/react";
import Timer from "./Timer";
test("초 표시", () => {
Date.now = jest.fn(() => 123456789); // 항상 변하는 값은 mock 함수를 사용한다.
const view = render(<Timer />);
expect(view).toMatchSnapshot();
});
색이나 리스트의 갯수나 텍스트를 비교하는데 한줄 한줄 테스트 코드를 짜는 것은 비효율적이라 스냅샷 테스트를 하는 것이 좋다. 하지만 불필요한 테스트들이 많아질 수 있다.
ui가 자주 바뀌는 경우에는 테스트를 추천하지 않음.(항상 실패함)
출처: 코딩앙마(https://www.youtube.com/playlist?list=PLZKTXPmaJk8L1xCg_1cRjL5huINlP2JKt)