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

이때 필요한 것이 바로 모의(Mock) 함수입니다.말 그대로 ‘가짜’이기 때문에 네트워크 비용 없이 바로 특정 결과를 반환하게 하거나 의도적으로 에러를 던지거나 함수가 몇 번 호출되었는지 감시하거나 등등 원하는 대로 마음껏 주무를 수 있습니다.따라서 방해 요인을 최소화하고 온전히 테스트에만 집중할 수 있습니다.

개인적으로 테스트에서 처음 ‘Mock(모의)’란 단어를 접했을 때 이해하기 참 어려웠는데 사실은 ‘모의고사’, ‘모의실험’, ‘모의훈련’, ‘목업(Mock-up)’ 등 평소에 많이 접할 수 있는 단어입니다.

[모의]: 실제의 것을 흉내 내어 그대로 해 봄.

간단하게 실제 Todo API를 사용하여 컴포넌트를 구성해 봅시다.다음은 Axios로 Todo 정보를 가져와서 화면에 제목(title)을 출력하는 예제입니다.

<!-- HelloJest.vue -->

<template>
  <div>
    {{ todo.title }}
  </div>
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      todo: {}
    }
  },
  created () {
    this.fetchTodo()
  },
  methods: {
    async fetchTodo () {
      //https://jsonplaceholder.typicode.com/const { data } = await axios.get('<https://jsonplaceholder.typicode.com/todos/1>')
      this.todo = data
    }
  }
}
</script>

다음은 실제 응답 결과의 data 값입니다.title 속성만 확인합니다!

{
  "completed": false,
  "id": 1,
  "title": "delectus aut autem",
  "userId": 1
}

이제 다음과 같이 테스트를 작성합니다.주의할 점은, 실제 요청/응답을 통해야 하므로 화면에 내용이 반영될 수 있는 대략적인 시간을 고려해야 합니다.따라서 **setTimeout**을 사용해 1초 후 테스트하도록 작성했습니다.네트워크가 속도가 느리면 2초, 3초 등으로 시간을 늘려야 할 수 있겠네요.

// HelloJest.test.js

import { shallowMount } from '@vue/test-utils'
import HelloJest from '../HelloJest'

describe('HellJest', () => {
  let wrapper
  beforeEach(() => {
    wrapper = shallowMount(HelloJest)
  })
  test('가져온 텍스트가 정상적으로 렌더링', done => {
    setTimeout(() => {
      expect(wrapper.text()).toBe('delectus aut autem')
      done()
    }, 1000) // 1초
  })
})

일반적인 네트워크 속도라면 위 테스트는 통과합니다.하지만 실제 요청/응답을 사용하기 때문에 만약 네트워크가 오프라인이면 테스트는 실패하며, 이는 외부 요인에 의한 실패가 됩니다.또한, 네트워크가 온라인이지만 속도가 아주 느리거나 API 서버에 문제가 발생하거나 등등 다양한 외부 요인에 의해 테스트는 실패할 수 있습니다.

그렇다면 이런 외부 요인(네트워크 비용)을 제거하기 위해 **axios.get()**을 모의 함수로 만들어(Mocking) 필요한 값만 반환하게 만들어 봅시다.실제 요청에서는 title 속성이 포함된 todo 객체를 응답받기 때문에 똑같이 작성합니다.다음과 같이 수정합니다.

// HelloJest.test.js

import { shallowMount } from '@vue/test-utils'
import axios from 'axios'
import HelloJest from '../HelloJest'

describe('HellJest', () => {
  let wrapper
  beforeEach(() => {
    // 반환할 가짜 응답 결과
    const response = {
      data: {
        title: 'delectus aut autem'
      }
    }
    // 컴포넌트가 마운트되기 전에 Mocking합니다.
    // axios.get()은 Promise를 반환하기 때문에 mockResolvedValue를 사용합니다.
    axios.get = jest.fn().mockResolvedValue(response)
    // 위 코드는 다음과 같습니다.
    // axios.get = jest.fn(() => new Promise(resolve => resolve(response)))

    // 컴포넌트 마운트!
    wrapper = shallowMount(HelloJest)
  })
  test('가져온 텍스트가 정상적으로 렌더링', () => {
    // 네트워크에 의존할 필요가 없어서(모의 요청/응답이기 때문에) 더는 setTimeout이 필요하지 않습니다.
    expect(wrapper.text()).toBe('delectus aut autem')
  })
})

이어서 설명합니다.

jest.fn()

위에서 살펴본 것과 같이 **jest.fn()**은 모의 함수를 반환하는데, 이 반환될 모의 함수의 구현을 함께 작성할 수 있습니다.또한 모의 함수는 기본적으로 호출이 감시되며, 추가로 사용할 수 있는 여러 메소드도 가지고 있습니다.

우선, 위에서 작성했던 예제를 기준으로 함수 구현 작성에 대해서 알아봅시다.실제로 **axios.get**은 비동기로 데이터를 가져오며, Promise 객체를 반환합니다.우리는 **axios.get**을 네트워크 비용을 사용하지 않도록 모의 함수로 만들지만, 컴포넌트의 로직이 동작해야 하니 똑같이 Promise가 반환되는 구조를 만들어줘야 합니다.따라서 다음과 같이 **jest.fn()의 첫 번째 인수로 Promise가 반환될 수 있는 콜백 함수를 만들어 줍니다.jest.fn()**은 모의 함수를 반환하기 때문에 **.mockImplementation()**을 사용할 수 있습니다.

모의 함수에 사용할 수 있는 여러 메소드는 아래에서 정리합니다.

axios.get = jest.fn(() => new Promise(resolve => resolve(response)))
// Or
// axios.get = jest.fn().mockImplementation(() => new Promise(resolve => resolve(response)))

특별한 구현이 없고 원하는 값(Promise)만 반환하면 되기 때문에,위 방법을 .mockResolvedValue() 메소드로 대체할 수 있습니다.