Jest는 페이스북에서 만들었으며, React와 더불어 많은 자바스크립트 개발자들이 사용하는 테스팅 라이브러리이다.
실제로 Mocha, Chai 등 자바스크립트를 테스트하는 라이브러리는 많다.
페이스북에서는 Jest를 단순히 테스팅 라이브러리가 아닌 테스팅 프레임워크 라고 부르는 만큼 기존 자바스크립트 테스팅 라이브러리와는 차이가 있다.
Jest 이전에는 자바스크립트를 테스트하기 위해 여러 라이브러리를 조합하여 사용해야했다.
Mocha나 Jasmin을 Test Runner로 사용하고, Chai나 Expect와 같은 Test Matcher를 사용했으며, 또한 Sinon과 Testdouble 같은 Test Mock 라이브러리도 필요했었다고 한다.
하지만 Jest는 위의 모든 기능을 다 가지고 있고, Test Mock 프레임워크까지 제공해주어 굉장히 편리하다.
[파일명].test.js
위와 같이 파일 이름을 설정하면 테스트 파일이 생성된다. ex)fn.test.js
또한 테스트 파일을 모아놓은 디렉토리를 생성하고 싶다면 __test를 생성하여 넣으면 된다.
__test 폴더에 있는 파일들은 모두 테스트 파일로 인식하기 때문이다.
npm init -y
npm install jest --save-dev
npm으로 jest를 설치한 다음 package.json의 script를 변경해야한다.
"scripts": {
"test": "jest"
},
📌 CRA로 만든 프로젝트는 React Testing Library가 설치되어 있고 이는 Jest 기반이기 때문에 npm으로 Jest를 따로 설치할 필요가 없다.
test()와 it()은 하나의 테스트를 만드는 기능을 수행한다. 기본적으로 아래와 같이 사용하면 된다.
// fn.test.js
test('1은 1이야', ()=> {
expect(1).toBe(1);
})
it('1은 1이야', () => {
expect(1).toBe(1);
})
test()와 it()은 개발자의 취향에 따라 사용하면 되며 둘의 기능은 완벽하게 일치한다.
위의 코드에서 expect()에는 검증 대상이 들어가고 toBe()에는 기대 결과가 들어간다.
그리고 toBe()와 같은 함수를 Test Matcher라고 한다.
matcher는 정말 다양한데, 필요에 따라 Jest 공식 문서로 가서 읽어보면서 사용하면 된다.
// fn.js
const fn = {
add : (num1, num2) => (num1 + num2),
}
module.exports = fn;
// fn.test.js
const fn = require('./fn');
test('2+3은 5', ()=> {
expect(add(2, 3)).toBe(5);
});
test('2+3은 5', ()=> {
expect(add(2, 3)).toEqual(5);
})
위의 두 테스트를 실행해보면 둘 다 통과하게 된다. 그럼 toBe()와 toEqual()은 같은 기능을 하는 함수일까❓
아니다❌
아래의 경우에는 다르다.
// fn.js
const fn = {
makeUser:(name, age) => ({name, age})
}
module.exports = fn;
// fn.test.js
test('이름과 나이를 전달받아서 객체를 반환해줘zz', () => {
expect(fn.makeUser("mike", 30)).toBe({
name:"mike",
age:30
})
})
test('이름과 나이를 전달받아서 객체를 반환해줘ss', () => {
expect(fn.makeUser("mike", 30)).toEqual({
name:"mike",
age:30
})
})
위에서 알 수 있듯이 toBe()에서는 테스트를 통과하지 못한다. 왜그럴까?
Jest는 객체나 배열을 재귀적으로 돌면서 값을 확인하기 때문에 toBe()가 먹히지 않는다고 한다.
따라서 객체나 배열을 테스트할 때는 toBe()를 사용하지 않아야 한다.
그럼 toEqual()은 문제가 없을까?
아래의 경우를 보자.
// fn.js
const fn = {
makeUser:(name, age) => ({name, age, gender:undefined})
}
module.exports = fn;
// fn.test.js
test('이름과 나이를 전달받아서 객체를 반환해줘ss', () => {
expect(fn.makeUser("mike", 30)).toEqual({
name:"mike",
age:30
})
})
위의 테스트를 실행했을 때, 대부분의 개발자들은 테스트를 통과하지 않아야한다고 생각할 것이다. 하지만 위의 경우 테스트를 통과하게 된다.
toEqual()은 깊은 비교를 하지 않기 때문에 정확한 테스트를 할 수 없다.
toEqual()의 문제를 위에서 확인했다.
깊은 비교를 통해 해결할 수 있는 방법이 toStrictEqual()을 사용하는 것이다.
아래의 코드를 보자.
// fn.test.js
test('이름과 나이를 전달받아서 객체를 반환해줘dd', () => {
expect(fn.makeUser("mike", 30)).toStrictEqual({
name:"mike",
age:30
})
})
테스트를 통과하지 못하는 것을 알 수 있다. 정상적으로 동작한다.
boolean 값을 판단하는 matcher다.
// fn.test.js
test('0은 false입니다.', () => {
expect(fn.add(1, -1)).toBeFalsy();
// expect(fn.add(1, -1)).toBeTruthy(); >> test 통과 X
})
아래의 코드를 먼저 실행해보면,
// fn.test.js
test("0.1 더하기 0.2는 0.3입니다.", () => {
expect(fn.add(0.1, 0.2)).toBe(0.3);
})
테스트를 통과하지 못한다. 위의 실행결과를 자세히 보면 Received가 0.30000000...4로 되어있음을 알 수 있다. 이것 때문에 테스트를 통과하지 못했다. 왜 이런 현상이 발생할까?
컴퓨터는 이진법을 사용한다. 소수를 이진법으로 바꿨을 때, 몇몇 소수들은 무한 소수가 되어버려서 발생하는 현상이다. 따라서 소수를 테스트할 때는 toBe()를 사용할 수 없다.
따라서 아래와 같이 작성하고 실행할 수 있다.
// fn.test.js
test("0.1 더하기 0.2는 0.3입니다.", () => {
expect(fn.add(0.1, 0.2)).toBeCloseTo(0.3);
})
테스트를 통과하는 것을 알 수 있다.
toMatch()는 정규표현식을 사용하여 테스트를 할 때 사용한다.
// fn.test.js
test("hello world에 a라는 글자가 있나?", ()=> {
expect("hello world").toMatch(/a/);
}) // 테스트 통과 X
test("hello world에 H라는 글자가 있나?", ()=> {
expect("hello world").toMatch(/a/);
}) // 테스트 통과 X
위의 두 테스트 모두 통과하지 못하며, 두 번째 테스트를 통해서 대소문자 구분을 한다는 것을 알 수 있다.
배열에 특정 요소가 존재하는지 테스트를 할 때 사용하는 matcher이다.
아래와 같이 사용한다.
// fn.test.js
test("유저리스트에 mike가 있나?", () => {
const userName = "mike"
const userList = ["Tom", "mike", "alice"];
expect(userList).toContain(userName);
})
어떠한 동작을 했을 때 특정 에러가 발생하는지 테스트를 하기 위한 matcher이다.
// fn.js
const fn = {
throwErr:() => {
throw new Error("xx");
}
}
module.exports = fn;
// fn.test.js
test("이거 에러 나나요?", () => {
expect(() => fn.throwErr()).toThrow("oo") // >> 테스트 통과 X
// expect(() => fn.throwErr()).toThrow("xx");
})
이외에도 정말 많은 matcher들이 있다. 필요할 때 마다 Jest 공식 문서를 참고하면 될 것 같다.
🖐🖐🖐🖐🖐