본문 바로가기
FRONTEND/Javascript

210425_Jest 사용법

by 또야또야 2021. 5. 28.
반응형

Jest 기본 사용법

Spharos CMP 프로젝트의 Jest 는 package.json dependencies 내부에 @vue-plugin-e2e-cypress 로 정의되어있으며, 하단 스크립트로 실행 할 수 있습니다.

--mode 는, 개발기 dev, 배포용 prod 으로 변경할 수 있습니다.

    "scripts": {
      "test:unit": "vue-cli-service test:unit --mode prod"
    },
    "devDependencies": {
        "@vue/cli-plugin-unit-jest": "version..."
    }
    $ npm run test:unit <Test File Name>

Test 파일 구성

모든 테스트 코드를 깔끔하게 /tests 디렉터리에 집어넣는 방법을 주로 사용하며, 작성된 테스트 코드의 종류(단위 테스트 / 통합 테스트)를 고려해야합니다. 현재 CMP 프로젝트의 /tests 파일 구성은 아래와 같습니다.

    ㄴ tests
      ㄴ e2e # e2e 테스트 파일이 포함됩니다
      ㄴ unit # 단위 테스트 파일이 포함됩니다
      ㄴ integration # 통합 테스트 파일이 포함됩니다

Test File 이름 짓기

테스트 종류에 상관없이 *.test.js 는 명확하게 이해할 수 없으므로, 테스트 종류를 파일 이름 앞에 포함시킵니다.

  • 단위 테스트 : index.unit.test.js
  • 통합 테스트 : api.int.test.js

Jest 코드 작성

Jest는 스냅샷 테스트 (Snapshot test) 이라는 기능도 갖고있습니다.

스냅샷 테스트는 UI가 의도치않게 변경되지 않도록 상태를 유지하고싶을 때 사용합니다.

전형적인 스냅샷 테스트는 UI 컴포넌트를 랜더링해서, 스냅샷을 찍은 후, 스냅샷 파일을 저장된 테스트 파일과 비교합니다.
테스트는 두개의 스냅샷을 비교해서 일치하지 않을 경우(변화가 예상되지 않거나, 참조할 스냅샷이 새로운 버전의 UI 컴포넌트가 필요한 경우 둘중 하나) 실패합니다.

Jest 로 스냅샷 테스트하기

스냅샷 테스트 문서

비동기 테스트 패턴

프로젝트에서 비동기 코드를 작성하게 될 경우도 있으므로, Jest 에서 비동기 코드를 쉽게 테스트 할 수 있습니다.

  // PromiseTestSample.unit.spec.js
  function asyncFn (hasToFail) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (hasToFail) reject(new Error('Test Failed! 😢'))

        resolve('Test Passed!! 🥳')
      }, 500) // Test 함수는 최대 5초 까지 기다리므로, 5초 이후부터는 테스트에 실패합니다.
    })
  }

  // 다음 테스트는 모두 통과합니다.
  describe('비동기 함수 테스트 1!', () => {
    /*
      test에서 done을 인수로 사용하는경우, done 이 호출될 때까지 테스트는 기다립니다.
      done 이 호출되지 않으면 테스트는 기본 0.5초 후 실패합니다.
    */
    test('비동기 fullfilled?', done => {
      asyncFn().then(result => {
        expect(result).toBe('Test Passed!! 🥳')
        done()
      })
    })

    /*
      test()에서 Promise 를 반환하면, 그 Promise 가 이행(resolved) 될 때까지 기다립니다.
      거부(reject) 되면 테스트는 실패합니다.
    */
    test('return Promise', () => {
      return asyncFn().then(result => {
        expect(result).toBe('Test Passed!! 🥳')
      })
    })

    /*
      받은 값(expect와 기댓값(Matcher) 사이에 2 개의 Bridge 속성(.not 과 같이) 을 사용할 수 있습니다.
      resolves 는 받은 Promise가 이행될 때 까지 기다리고, rejects 는 거부될 때까지 기다립니다.
      단언 (expect() 구문) 을 반환해야 테스트는 기다립니다.
    */
    test('resolves', () => { // resolve Test
      return expect(asyncFn()).resolves.toBe('Test Passed!! 🥳')
    })
    test('rejects', () => { // reject Test
      return expect(asyncFn('hasToFail')).rejects.toThrow('Test Failed! 😢')
    })

    /*
      test() 의 콜백을 비동기 함수 (async function) 으로 선언할 경우,
      테스트는 작성된 await 에 맞게 기다립니다.
    */
    test('async/await', async () => {
      const result = await asyncFn()
      expect(result).toBe('Test Passed!! 🥳')
    })
  })

test() 함수는 테스트 완료까지 기본적으로 최대 5 초 까지 기다립니다. 5 초 이상이 걸리는 경우는 테스트가 실패하므로 상황에 따라서는 기다리는 시간을 늘려줄 필요가 있습니다. test() 의 세 번째 매개변수는 대기할 시간 (ms) 를 입력합니다.

  // PromiseTestSample.unit.spec.js
  // Promise 테스트 function 예제 (5 초 이상 걸릴 경우) 입니다.
  function asyncFnOverTime (hasToFail) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (hasToFail) reject(new Error('Test Failed! 😢'))

        resolve('Test Passed!! 🥳')
      }, 8000) // 8 초 까지 걸릴 수 있는 코드를 설정해주었습니다
    })
  }

  describe('비동기 함수 테스트 - 대기시간', () => {
    /**
    * test 함수는 최대 { 8 } 초 까지 기다립니다.
    * @function test()
    * @param {String} name
    * @param {Function} callback
    * @param {Number} time 해당 시간만큼 대기합니다.
    */
    test('timeout 8000', async () => {
      const result = await asyncFnOverTime() // timeout이 8 초로 설정되어있는 함수
      expect(result).toBe('Test Passed!! 🥳')
    }, 8000)
  })

테스트 더블 (Test double)

테스트 환경에서는 실제 구현한 함수를 그대로 사용할 수 없는 경우가 많습니다.
네트워크 비용이 발생하는 외부 API에 의존하는 함수라던가 함수 실행 후 불필요하게 많은 상태(데이터)가 변경되거나 등등 테스트 자체에 집중할 수 없게 방해되는 외부 요인들은 얼마든지 있습니다.

이러한 경우, 영화 제작의 스턴트 대역 (Stunt Double) 이 실제 배우를 대신하는 것 처럼 테스트를 위해 실제 객체를 대신하는 것을 의미합니다. 개념에 대해서는 이전 포스트 를 참고하세요

용어 설명
더미(Dummy) 실제로는 동작하지 않고 데이터를 채우기 위한 객체
스텁(Stub) 실제로는 동작하지 않지만, 동작하는 것 처럼 보이기 위해 준비된 값만을 반환하는 객체
모조품(Fake) 실제로 동작하지만 프로덕트에는 적합하지 않은 객체
모의(Mock) 실제로 동작하지만 준비된 값 만을 반환하는 객체
스파이(Spy) 호출 등 일부 정보를 기록해 다양한 상황을 감시하는 객체

모의 함수 만들기 (Mocking)

다양한 요인으로 인해 테스트가 어려운 상황에서 필요한 것이 모의 (Mock) 함수인데, 가짜 데이터이기 때문에 네트워크 비용 없이 바로 결과를 반환하게 한다거나, 의도적으로 에러를 던지는 등 자유롭게 사용할 수 있습니다.

Jest 에서는 Mock Functions APIMock Functions 사용법 을 제공합니다.

  // MockingTest.unit.spec.js
  // Test Function
  function forEachFn (items, callback) {
    for (let idx = 0; idx < items.length; idx++) {
      callback(items[idx])
    }
  }

  // Jest - Mock Function
  describe('Jest Mocking Function 테스트', () => {
    test('모의 (mock) callback 테스트', () => {
      const mockCallback = jest.fn(x => 45 + x)
      const array = [0, 1]
      forEachFn(array, mockCallback)

      console.log(mockCallback.mock)

      // forEachFn 을 [0, 1] 두번 돌면서 몇번 호출되었는지 확인합니다.
      expect(mockCallback.mock.calls.length).toBe(2)

      // 처음 호춛되었을때 argument는 0 인지 확인합니다.
      expect(mockCallback.mock.calls[0][0]).toBe(0)

      // 처음 호출 되었을 때 리턴 값은 42 였는지 확인합니다.
      expect(mockCallback.mock.results[0].value).toBe(45)
    })
  })

SpyOn 사용하기 (Spying)

jest.spyOn 은 실제 함수를 모의 함수로 덮어쓰지 않고도 호출 감시를 목적으로 유용하게 사용됩니다.

Jest 에서는 spyOn API 을 제공합니다. spyOn 함수도 mock 프로퍼티를 반환합니다.

  // MockingTest.unit.spec.js
  // Test Object
  const video = {
    play () {
      return true
    }
  }
  const audio = {
    _volume: false,
    get volume () {
      return this._volume
    },
    set volume (value) {
      this._volume = value
    }
  }

  // Jest - SpyOn
  describe('Spy On Object 테스트', () => {
    test('simple plays video', () => {
      const spy = jest.spyOn(video, 'play')
      // 또는 :: const spy = jest.spyOn(video, 'play', 'get')
      const isPlaying = video.play()

      expect(spy).toHaveBeenCalled()
      expect(isPlaying).toBe(true)
      console.log('simple video :: ', spy.mock)

      spy.mockRestore() // 모의 함수의 모든 상태 초기화 + 복원
    })

    test('set audio volume', () => {
      const spy = jest.spyOn(audio, 'volume', 'set')
      audio.volume = 100

      expect(spy).toHaveBeenCalled()
      expect(audio.volume).toBe(100)
      console.log('get/set audio :: ', spy.mock)

      spy.mockRestore() // 모의 함수의 모든 상태 초기화 + 복원
    })
  })

출처 및 참고자료

반응형

'FRONTEND > Javascript' 카테고리의 다른 글

210628 Javascript Optional Chanining 활용  (0) 2021.06.28
ES2021 새로운 문법  (0) 2021.06.28
Map, WeakMap, Set, WeakSet  (0) 2021.05.09
bundle size 와 Javascript Performance  (0) 2021.03.03
5) Typescript - Classes  (0) 2021.02.08

댓글