Jest


테스팅 라이브러리

  • 공식문서
  • 🔦 기본 세팅

  • 설치
  • npm i -D jest
  • package.json
  • "scripts": {
    "test": "jest"
    },
  • Jest는 *.test.* 의 형태를 가진 파일을 테스트 파일로 인식한다.
  • 🍮 테스트 함수 작성하기

  • calculate.js
  • function sum(a, b) {
    return a + b;
    }
    module.exports = sum;
  • calculate.test.js
  • const sum = require('./calculate');
    test('더하기 함수 테스트', () => {
    expect(sum(1, 2)).toBe(3);
    });
  • 이후 테스트를 실행하려면 npm test 를 해주면 된다.
  • 하나의 test에서 여러 줄의 expect를 사용하는 것도 가능하다.
  • 🍦 toBe, toEqual

  • toBe는 앝은 비교== 이고, toEqual은 깊은 비교=== 이다.
  • toBe로 비교

    const makeObj = (id, name) => {
    return { id, name };
    };
    test('toBe의 문제점', () => {
    expect(makeObj('jke', '장경은')).toBe({ id: 'jke', name: '장경은' });
    });
    If it should pass with deep equality, replace "toBe" with "toStrictEqual"
    Expected: {"id": "jke", "name": "장경은"}
    Received: serializes to the same string
    4 |
    5 | test('toBe의 문제점', () => {
    > 6 | expect(makeObj('jke', '장경은')).toBe({ id: 'jke', name: '장경은' });
    | ^
    7 | });
    8 |
    at Object.toBe (toEqual.test.js:6:33)

    toEqual로 비교

    const makeObj = (id, name) => {
    return { id, name };
    };
    test('toEqual로 변경', () => {
    expect(makeObj('jke', '장경은')).toEqual({ id: 'jke', name: '장경은' });
    });
    PASS ./toEqual.test.js
    ✓ toEqual로 변경 (1 ms)
    Test Suites: 1 passed, 1 total
    Tests: 1 passed, 1 total
    Snapshots: 0 total
    Time: 0.152 s, estimated 1 s
  • 그러나 아래와 같이 undefined인 key가 있을 때에도 toEqual은 테스트를 패스시킨다.
  • const makeObj = (id, name) => {
    return { id, name, age: undefined };
    };
    test('toEqual의 한계', () => {
    expect(makeObj('jke', '장경은')).toEqual({ id: 'jke', name: '장경은' });
    });
    PASS ./toEqual.test.js
    ✓ toEqual의 한계 (1 ms)
    Test Suites: 1 passed, 1 total
    Tests: 1 passed, 1 total
    Snapshots: 0 total
    Time: 0.15 s, estimated 1 s

    toStrictEqual

  • 이러한 경우에는 toStrictEqual을 사용하면 된다.
  • const makeObj = (id, name) => {
    return { id, name, age: undefined };
    };
    test('toStrictEqual로 변경', () => {
    expect(makeObj('jke', '장경은')).toStrictEqual({ id: 'jke', name: '장경은' });
    });
    Test Suites: 1 failed, 1 total
    Tests: 1 failed, 1 total
    Snapshots: 0 total
    Time: 0.17 s, estimated 1 s

    🍬 TS에서 사용

  • 설치
  • npm i -D ts-jest @types/jest
    npx ts-jest config:init

    → jest.config.js 파일이 생성됨

    테스트 케이스 생성

  • 아래처럼 ts 문법에 맞춰 작성하면 된다.
  • const sum = (a: number, b: number): number => {
    return a + b;
    };
    test('타입스크립트 함수 테스트', () => {
    expect(sum(1, 2)).toBe(3);
    });
    PASS ./ts.test.ts
    ✓ 타입스크립트 함수 테스트
    Test Suites: 1 passed, 1 total
    Tests: 1 passed, 1 total
    Snapshots: 0 total
    Time: 0.877 s

    🍿 Matchers

  • 여러 Matcher를 학습하기 위해 몇가지 함수를 만들었다.
  • interface User {
    email: string;
    name: string;
    age: number;
    }
    const users: User[] = [
    { email: 'j5@naver.com', name: '장경은', age: 27 },
    { email: 'sh@naver.com', name: '박성희', age: 27 },
    { email: 'sa@naver.com', name: '신상아', age: 31 },
    { email: 'tetz@naver.com', name: '이효석', age: 39 },
    ];
    const getUser = (): User[] => {
    return users;
    };
    const getUserNumByAge = (age: number): number => {
    const filteredUser: User[] = users.filter(el => el.age >= age);
    return filteredUser.length;
    };
    const getEmailByName = (name: string) => {
    const filteredUser: User[] = users.filter(el => {
    if (el.name === name) return el.email;
    });
    const result: string = filteredUser.length > 0 ? filteredUser[0].email : '존재하지 않는 이름입니다.';
    return result;
    };

    toHaveLength

  • 특정 배열의 길이를 테스트
  • test('전체 회원의 수는 4명인가?', () => {
    expect(getUser()).toHaveLength(4);
    });

    toContainEqual

  • 특정 배열 값에서 원하는 배열이 존재하는지 확인
  • 객체가 아닌 원시 값을 테스트 할 때에는 toContain을 사용해도 된다.
  • test('전체 회원 중에 아래의 회원 정보를 가진 회원이 존재 하는가?', () => {
    expect(getUser()).toContainEqual({
    email: 'tetz@naver.com',
    name: '이효석',
    age: 39,
    });
    });

    숫자 비교 Matchers

  • toBeGreaterThan / toBeGreaterThanOrEqual
  • toBeLessThan / toBeLessThanOrEqual
  • test('25살 이상인 회원은 3명이 넘는가?', () => {
    expect(getUserNumByAge(25)).toBeGreaterThan(3);
    });
    // 4명이므로 pass
    // 그러나 4를 입력하면 failed

    toMatch

  • 정규식을 이용하여 문자열 검사를 할 수 있다.
  • test('특정 이름을 가진 회원의 email 주소 형태가 올바른가?', () => {
    expect(getEmailByName('장경은')).toMatch(/^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$/);
    });

    toThrow

  • 에러가 발생하는 경우 에러를 테스트할 수 있다.
  • 에러를 발생시키는 함수를 익명함수로 감싸주어야 한다.
  • const throwErr = (): never => {
    throw new Error('에러 발생!');
    };
    test('에러가 발생하는지 테스트', () => {
    expect(() => throwErr()).toThrow();
    });

    not

  • matcher의 기대값을 반대로 변경한다.
  • test('null', () => {
    const n = null;
    expect(n).toBeNull();
    expect(n).not.toBeUndefined();
    });
  • 이렇게 작성하면 undefined가 아닐 경우 테스트가 통과한다.
  • 외에도 많은 Matcher들이 있다.
  • 🍥 비동기 테스트

  • Callback / Promise / Async-Await도 테스트가 가능하다.
  • Callback

    const getNameCB = (callback: (str: string) => void): void => {
    const name: string = 'jke';
    setTimeout(() => {
    callback(name);
    }, 2000);
    };
  • 위와 같은 콜백 함수를 하나 선언해준다.
  • test('2초 뒤에 이름을 받아오는 콜백 함수 테스트', done => {
    function callbackFunc(name: string): void {
    expect(name).toBe('jke');
    done();
    }
    getNameCB(callbackFunc);
    });
  • 위의 코드를 테스트하기 위해서는 위처럼 expect를 함수로 감싸주면 된다.
  • 또한 테스트에 done을 인자로 전달하여 안에서 실행시켜준다.
  • 에러 핸들링은 다음과 같이 해줄 수 있다.
  • const getNameCB = (callback: (str: string) => void): void => {
    const name: string = 'jke';
    setTimeout(() => {
    try {
    if (Math.floor(Math.random() * 2) % 2 === 0) {
    console.log('정상 케이스');
    callback(name);
    } else {
    console.log('에러');
    }
    } catch (err) {
    callback(err);
    }
    }, 2000);
    };
    test('비동기 콜백 함수 테스트', done => {
    function callbackFunc(data: any): void {
    try {
    if (data instanceof Error) {
    expect(data.message).toBe('에러');
    } else {
    expect(data).toBe('jke');
    }
    done();
    } catch (err) {
    done(err);
    }
    }
    getNameCB(callbackFunc);
    });

    Promise

    const getNamePromise = (): Promise<string> => {
    const name = 'jke';
    return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
    resolve(name);
    }, 2000);
    });
    };
    test('2초 후에 이름을 받아오는 프로미스 함수 테스트', () => {
    return getNamePromise().then((name: string) => {
    expect(name).toBe('jke');
    });
    });
  • getNamePromise를 return으로 해주어야 한다.
  • 에러 핸들링은 아래와 같이 할 수 있다.
  • const getNamePromise = (): Promise<string> => {
    const name = 'jke';
    return new Promise<string>((resolve, reject) => {
    setTimeout(() => {
    if (Math.floor(Math.random() * 2) % 2 === 0) {
    resolve(name);
    } else {
    reject(new Error('에러'));
    }
    }, 2000);
    });
    };
    test('2초 후에 이름을 받아오는 프로미스 함수 테스트', () => {
    return getNamePromise()
    .then((name: string) => {
    expect(name).toBe('jke');
    })
    .catch(err => {
    expect(err.message).toBe('에러');
    });
    });

    Async / Await

  • 에러 핸들링
  • const getNameAsync = (id: string): Promise<string> => {
    return new Promise<string>((res, rej) => {
    setTimeout(() => {
    if (id === 'jke') {
    console.log('정상 케이스');
    res('장경은');
    } else {
    console.log('에러 케이스');
    rej(new Error('id가 다릅니다.'));
    }
    }, 2000);
    });
    };
    test('2초 후에 이름을 async/await로 받아오는 함수 테스트', async () => {
    try {
    const result: any = await getNameAsync('jke');
    expect(result).toBe('장경은');
    } catch (err) {
    expect(err.message).toBe('id가 다릅니다.');
    }
    });

    🚦TDD

  • TDD란 Test Driven Development 의 약자로 개발 방법론 중 하나이다.
  • TDD의 핵심은 테스트 코드를 먼저 작성하고, 실제 코드를 작성하는 것이다.
  • TDD는 Red-Green-Blue의 3가지 단계를 거친다.
  • TDD 방식으로 개발을 하게되면 1️⃣ 코드 작성과정에서 확신을 얻고 개발할 수 있으며, 2️⃣ 구현이 잘못된 경우 바로 확인이 가능하고, 3️⃣ 테스트 코드를 작성하며 실제 코드를 미리 계획할 수 있다는 이점이 있다.