View

배경

TDD를 학습하며 Jest의 기본 문법을 정리한다.

 

Jest

Facebook에서 만든 테스팅 프레임워크
"Delightful Javascript Testing"
https://jestjs.io/

 

시작하기

jest 설치

$ npm run i -D jest

babel 설정

$ yarn add --dev babel-jest @babel/core @babel/preset-env
// babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        targets: {
          node: 'current',
        },
      },
    ],
  ],
};

typescript 설정

$ yarn add --dev @babel/preset-typescript
// babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', {targets: {node: 'current'}}],
+    '@babel/preset-typescript',
  ],
};

스크립트 설정

// package.json
{
 //...
 "scripts": {
   "test": "jest"
 },
 // ...
}

 

디렉토리 구조 & 예시 코드
스크립트 실행 결과

 

기본 문법

Grouping

const myBeverage = {
  delicious: true,
  sour: false,
};

// Unit Test 모음
describe('my beverage', () => { 
  // Unit Test
  test('is delicious', () => {
    expect(myBeverage.delicious).toBeTruthy();
  });
  // Unit Test (it은 test의 alias입니다.)
  it('is not sour', () => {
    expect(myBeverage.sour).toBeFalsy();
  });
});

전/후 처리

const fn = () => {};
const timeout = 5000; // default

// timeout 은 how long to wait before aborting라는데 동작이.. 잘 모르겠음

// 모든 테스트의 전/후에 실행
beforeAll(fn, timeout)
afterAll(fn, timeout)

// 해당 테스트 전/후에 실행
beforeEach(fn, timeout)
afterEach(fn, timeout)

Matchers

/*
    주요 matchers
*/

const expectedResult = null; // 예상 결과값
toBe(expectedResult); // primitive 값만 비교 
toEqual(expectedResult); // object 값 비교
toBeTruthy(); // true 인지
toBeFalsy(); // false 인지
toThrow(); // 예외 발생 여부 확인

const arrayItem = null;
toContain(arrayItem); // === (strict check)했을 때 item이 array에 속할 때

const arrayItemObject = {key: 'value'};
toContainEqual(arrayItemObject); // array에 object item이 속할 때 (key-value 쌍 포함)

not.toXxx(); // 부정

const testTarget = null;
expect(testTarget).toThrow();
// 위 경우 testTarget은 함수여야합니다.

테스트 코드 선택 실행

// only : 해당 테스트만 실행
describe.only('', () => { /* ... */});
test.only('', () => { /* ... */});
it.only('', () => { /* ... */});

// skip : 해당 테스트만 제외하고 실행
describe.skip('', () => { /* ... */});
test.skip('', () => { /* ... */});
it.skip('', () => { /* ... */});

비동기 처리

setTimeout

function fetchUser(id, cb) {
  setTimeout(() => {
    const user = {/* ... */}
    cb(user);
  }, 200);
}
test('fetch a user', (done) => {
  fetchUser(1, (user) => {
    expect(user).toEqual(/* ... */);
    done(); // 비동기 코스트 테스트라는 걸 명시적으로 표시
  });
});

Promise

function fetchUser(id) {
  return new Promise((resolve) => {
    setTimeout(() => {
    const user = {/* ... */}
    resolve(user);
  }, 200);
  })
}
test('fetch a user', (done) => {
  // 1. Promise를 리턴  
  return fetchUser(1).then((user) => { 
    expect(user).toEqual(/* ... */);
  });

  // 2. 간단한 방법
  return expect(fetchUser(1)).resolves.toEqual(/* ... */);
});

async/await

function fetchUser(id) {
  return new Promise((resolve) => {
    setTimeout(() => {
    const user = {/* ... */}
    resolve(user);
  }, 200);
  })
}
test('fetch a user', (done) => {
  // 1. 기본 방법
  const user = await fetchUser(1);
  expect(user).toEqual(/* ... */);

  // 2. 간단한 방법
  await expect(fetchUser(1)).resolves.toEqual(/* ... */);
});

 

Mocking

단위 테스트 코드가 의존하는 데이터를 가짜(mock)로 대체하는 기법

  • mock object 생성 가능
  • 테스트를 실행하는 동안 mock object에 발생한 일들을 기억

 

Mock Functions

jest.fn()

가짜 함수 생성

import { register, deregister } from './userService';
import * as messageService from './messageService';

// 실제 sendEmail, sendSMS의 동작은 중요하지 않다.
// register, deregister에서 messageService의 sendEmail, sendSMS가 어떻게 불리는지를 테스트해야한다.
messageService.sendEmail = jest.fn();
messageService.sendSMS = jest.fn();

const sendEmail = messageService.sendEmail;
const sendSMS = messageService.sendSMS;

beforeEach(() => {
  sendEmail.mockClear();
  sendSMS.mockClear();
});

const user = {
  email: 'test@email.com',
  phone: '012-345-6789',
};

test('register sends messeges', () => {
  register(user);

  expect(sendEmail).toBeCalledTimes(1);
  expect(sendEmail).toBeCalledWith(user.email, '회원 가입을 환영합니다!');

  expect(sendSMS).toBeCalledTimes(1);
  expect(sendSMS).toBeCalledWith(user.phone, '회원 가입을 환영합니다!');
});

위 코드는 모듈의 함수들을 일일이 jest.fn()으로 mocking한다.

아래 코드는 jest.mock()을 사용하여 모듈을 전체 mocking하여 필요한 함수만 사용한다.

import { sendEmail, sendSMS } from "./messageService"

jest.mock('../src/messageService');
const { sendEmail, sendSMS } = require('../src/messageService');

beforeEach(() => {
  sendEmail.mockClear()
  sendSMS.mockClear()
})

 

jest.spyOn(object, methodName)

가짜 함수로 대체하지 않고, 특정 함수가 어떻게 호출되었는지를 확인

const calculator = {
  add: (a, b) => a + b,
}

const spyFn = jest.spyOn(calculator, "add")

const result = calculator.add(2, 3)

expect(spyFn).toBeCalledTimes(1)
expect(spyFn).toBeCalledWith(2, 3)
expect(result).toBe(5)

 

참고

Share Link
reply