Jest 란?

  • Facebook에서 만든 테스트 도구이다.
  • zero config 철학을 가지고 있기 때문에 별도의 설정 없이 빠르게 Test Case를 작성할 수 있다.

Test를 위한 프로젝트 세팅 및 설치

  • 나는 github/jest_tutorial 이라는 폴더에 프로젝트를 세팅했다.
  • package.json 세팅을 위해 npm init을 사용하고 jest를 설치한다.
  • jest는 테스트 도구로써 개발에서만 사용할 것이기 때문에 ‘devDependencies’ 에 추가한다.
npm init

npm install jest --save-dev

package.json

{
  "name": "jest_tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^29.7.0"
  }
}

테스트 케이스를 작성할 파일의 경우 ‘test.js’로 끝나거나, ‘tests’ 폴더에 있는 파일들은 자동으로 인식한다. 만약 직접 선택한 파일만 test하고 싶다면 npm test 뒤에 파일 명 혹은 경로를 입력하면 된다.

함수를 모아두고 테스트하기 위한 파일

fn.js

const fn = {
    add : (num1, num2) => num1 + num2,
    makeUser : (name, age) => ({name, age, gender : undefined}),
    throwErr : () => {
        throw new Error("xx");
    },
    getName: (callback) => {
        const name = "Mike";

        setTimeout(() => {
            callback(name);
        }, 3000);
    },
    getAge: () => {
        const age = 30;

        return new Promise((res, rej) => {
            setTimeout(() => {res(age)}, 3000);
        })
    },
    getAgeError: () => {
        const age = 30;

        return new Promise((res, rej) => {
            setTimeout(() => {
                rej('error')
            }, 3000)
        })
    }
}

module.exports = fn;

만들어둔 함수를 테스트 하기 위한 파일

fn.test.js

const fn = require('./fn');

test('1은 1이다.', () => {
    //expect : 검증할 값
    //toBe : 기대 값
    expect(1).toBe(1);
})

test('2 더하기 3은 5다.', () => {
    expect(fn.add(2, 3)).toBe(5);
});

test('2 더하기 3은 5다.', () => {
    expect(fn.add(2, 3)).toEqual(5);
});

test('3 더하기 3은 5가 아니다.', () => {
    expect(fn.add(3, 3)).not.toBe(5);
})

// (1) 객체나 배열은 재귀적으로 돌면서 값을 확인해야 하기 때문에 toEqual을 사용해야한다.
// 보다 엄격하게 검사하려면 toStrictEqual을 사용하는게 좋다.
test('이름과 나이를 전달받아서 객체를 반환해줘', () => {
    expect(fn.makeUser('Mike', 30)).toEqual(
        {
            name : 'Mike',
            age : 30,
        }
    );
})

// (2) 객체나 배열은 재귀적으로 돌면서 값을 확인해야 하기 때문에 toEqual을 사용해야한다.
// 보다 엄격하게 검사하려면 toStrictEqual을 사용하는게 좋다.
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("0은 false 이다.", () => {
    expect(fn.add("hello", "world")).toBeFalsy();
})

test("비어있지 않은 문자열은 true 이다.", () => {
    expect(fn.add("hello", "world")).toBeTruthy();
})

test("ID는 10자 이하여야 한다.", () => {
    const id = "THE_BLACK";
    expect(id.length).toBeLessThanOrEqual(10);
})

test("비밀번호는 4자리여야 한다.", () => {
    const password = '3414';
    expect(password.length).toBe(4);
})

test("비밀번호는 4자리여야 한다.", () => {
    const password = '3414';
    expect(password.length).toEqual(4);
})

// 이진법을 이용한 계산으로 몇몇 프로그래밍 언어들은 소수점을 정확하게 표현하지 못하기 때문에 무한소수로 출력되는 경우가 있다.
// 그러므로 소수점을 활용한 계산에서는 toBeCloseTo를 사용해서 test하는 것이 적절하다.
test('0.1 더하기 0.2는 0.3이다.', () => {
    expect(fn.add(0.1, 0.2)).toBeCloseTo(0.3);
})

test("Hello World 에 a 라는 글자가 있는가 ? ", () => {
    expect('Hello World').toMatch(/H/i);
})

test("유저 리스트에 Mike가 있는가?", () => {
    const user = "Mike";
    const userList = ["Tom", "Mike", "Kai"];

    expect(userList).toContain(user);
})

test("에러가 나는가?", () => {
    expect(() => fn.throwErr()).toThrow('xx');
})

//비동기 함수 테스트를 위해서는 test에 전달되는 callback 함수에 done을 전달해주면 된다.
//jest는 끝에 코드까지 도달하면 종료되기 때문에 done을 전달하지 않으면 제대로된 테스트가 진행되지 않는다.
test("3초 후에 받아온 이름은 Mike", (done) => {
    function callback(name){
        try {
            expect(name).toBe("Mike");
            done();
        } catch (error) {
            done();
        }
    }

    fn.getName(callback)
})

// Promise 를 테스트 할 경우
// Promise 를 사용하면 해당 코드를 return 해주어야 한다 그렇지 않으면 코드가 실행되고 바로 종료되므로 제대로된 테스트가 진행되지 않는다.
// 좀더 간단하게 사용하고 싶다면 Matcher를 사용하면된다 (resolves, rejects)
test("3초 후에 받아온 나이는 30", () => {
    return expect(fn.getAge()).resolves.toBe(30);

    // return fn.getAge().then(age => {
    //     expect(age).toBe(30);
    // })
})

// Promise에서 reject를 테스트할 경우
test("3초 후애 에러를 리턴", () => {
    return expect(fn.getAgeError()).rejects.toMatch('error');
})

npm test 결과

  jest_tutorial npm test

> jest_tutorial@1.0.0 test
> jest

 FAIL  ./fn.test.js (9.261 s)
   1 1이다. (1 ms)
   2 더하기 3 5.
   2 더하기 3 5. (1 ms)
   3 더하기 3 5 아니다.
   이름과 나이를 전달받아서 객체를 반환해줘
   이름과 나이를 전달받아서 객체를 반환해줘 (4 ms)
   null은 null이다. (1 ms)
   0 false 이다.
   0 false 이다.
   비어있지 않은 문자열은 true 이다.
   ID는 10 이하여야 한다.
   비밀번호는 4자리여야 한다.
   비밀번호는 4자리여야 한다.
   0.1 더하기 0.2 0.3이다. (1 ms)
   Hello World  a 라는 글자가 있는가 ?
   유저 리스트에 Mike가 있는가?
   에러가 나는가? (2 ms)
   3 후에 받아온 이름은 Mike (3002 ms)
   3 후에 받아온 나이는 30 (3002 ms)
   3 후애 에러를 리턴 (3001 ms)

   이름과 나이를 전달받아서 객체를 반환해줘

    expect(received).toStrictEqual(expected) // deep equality

    - Expected  - 0
    + Received  + 1

      Object {
        "age": 30,
    +   "gender": undefined,
        "name": "Mike",
      }

      34 | test('이름과 나이를 전달받아서 객체를 반환해줘', () => {
      35 |
    > 36 |     expect(fn.makeUser('Mike', 30)).toStrictEqual(
         |                                     ^
      37 |         {
      38 |             name : 'Mike',
      39 |             age : 30,

      at Object.toStrictEqual (fn.test.js:36:37)

   0 false 이다.

    expect(received).toBeFalsy()

    Received: "helloworld"

      51 |
      52 | test("0은 false 이다.", () => {
    > 53 |     expect(fn.add("hello", "world")).toBeFalsy();
         |                                      ^
      54 | })
      55 |
      56 | test("비어있지 않은 문자열은 true 이다.", () => {

      at Object.toBeFalsy (fn.test.js:53:38)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 18 passed, 20 total
Snapshots:   0 total
Time:        9.288 s
Ran all test suites.
  jest_tutorial

Jest에는 다양한 Matcher가 존재하고 Test할 함수나 기능에 따라 유용하게 사용할 수 있기 때문에 공식 Docs를 보면서 필요한 Matcher를 찾아보며 테스트를 진행하면 좋을거 같다.

Jest Matchers :

Using Matchers · Jest