모킹

모킹 (Mocking)

테스트를 작성하다 보면 내부든 외부든 어떤 서비스를 '가짜' 버전으로 만들어야 하는 때가 반드시 와요. 이걸 흔히 모킹(mocking) 이라고 불러요. Vitest 는 vi 헬퍼로 이 일을 도와주는 유틸 함수들을 제공해요. vitest 에서 import 하거나, global 설정을 켰다면 전역으로 접근할 수도 있어요. 이 페이지는 vi.spyOn·vi.mock·vi.stubGlobal 같은 도구를 익숙하지 않은 사람을 위한 입문이자, 자주 쓰는 패턴을 모아 둔 치트시트 역할을 해요.

출처: Vitest — Mocking

::: tip 모킹이 처음이라면 Mock Functions 튜토리얼부터 시작해 보세요. vi.fn, vi.spyOn, vi.mock 을 직접 따라 하며 배울 수 있어요. :::

::: warning 각 테스트 실행 전이나 후에 목을 반드시 clear 하거나 restore 해서 실행 사이의 목 상태 변경을 되돌리는 걸 잊지 마세요! 자세한 내용은 mockReset 문서를 참고하세요. :::

vi.fn, vi.mock, vi.spyOn 메서드가 익숙하지 않다면 먼저 API 섹션을 확인하세요.

Vitest 는 모킹에 관한 방대한 가이드 목록을 준비해 뒀어요:

모킹을 더 빠르고 간단하게 시작하고 싶다면 아래 치트시트를 보세요.

치트시트

"나는 ~을(를) 모킹하고 싶다"에서 시작해서 패턴을 골라 쓰면 돼요.

export 된 변수 모킹하기

export const getter = 'variable'
import * as exports from './example.js'

vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked')

::: warning 이 방법은 Browser Mode 에서 동작하지 않아요. 해결책이 필요하면 Limitations 를 참고하세요. :::

export 된 함수 모킹하기

  1. vi.mock 을 쓰는 예시:

::: warning vi.mock 호출은 파일 상단으로 호이스팅된다는 걸 잊지 마세요. 모든 import 보다 항상 먼저 실행돼요. :::

export function method() {}
import { method } from './example.js'

vi.mock('./example.js', () => ({
  method: vi.fn()
}))
  1. vi.spyOn 을 쓰는 예시:
import * as exports from './example.js'

vi.spyOn(exports, 'method').mockImplementation(() => {})

::: warning vi.spyOn 예시는 Browser Mode 에서 동작하지 않아요. 해결책은 Limitations 를 보세요. :::

export 된 클래스 구현 모킹하기

  1. 가짜 class 를 쓰는 예시:
export class SomeClass {}
import { SomeClass } from './example.js'

vi.mock(import('./example.js'), () => {
  const SomeClass = vi.fn(class FakeClass {
    someMethod = vi.fn()
  })
  return { SomeClass }
})
  1. vi.spyOn 을 쓰는 예시:
import * as mod from './example.js'

vi.spyOn(mod, 'SomeClass').mockImplementation(class FakeClass {
  someMethod = vi.fn()
})

::: warning vi.spyOn 예시는 Browser Mode 에서 동작하지 않아요. 해결책은 Limitations 를 보세요. :::

함수가 반환한 객체 감시하기

  1. 캐시를 이용하는 예시:
export function useObject() {
  return { method: () => true }
}
import { useObject } from './example.js'

const obj = useObject()
obj.method()
import { useObject } from './example.js'

vi.mock(import('./example.js'), () => {
  let _cache
  const useObject = () => {
    if (!_cache) {
      _cache = {
        method: vi.fn(),
      }
    }
    // now every time that useObject() is called it will
    // return the same object reference
    return _cache
  }
  return { useObject }
})

const obj = useObject()
// obj.method was called inside some-path
expect(obj.method).toHaveBeenCalled()

모듈 일부만 모킹하기

import { mocked, original } from './some-path.js'

vi.mock(import('./some-path.js'), async (importOriginal) => {
  const mod = await importOriginal()
  return {
    ...mod,
    mocked: vi.fn()
  }
})
original() // has original behaviour
mocked() // is a spy function

::: warning 이건 _외부 접근_만 모킹한다는 걸 잊지 마세요(Mocking Modules pitfalls 참고). 이 예시에서 original 이 내부에서 mocked 를 호출한다면, 모킹 팩토리 말고 항상 모듈 안에 정의된 함수를 호출해요. :::

현재 날짜 모킹하기

DateTemporal 의 시간을 모킹하려면 vi.setSystemTime 헬퍼를 쓰면 돼요. 이 값은 다른 테스트 사이에 자동으로 리셋되지 아요.

vi.useFakeTimers 를 쓰는 것도 Date 의 시간을 바꾼다는 점을 주의하세요.

const mockDate = new Date(2022, 0, 1)
vi.setSystemTime(mockDate)
const now = new Date()
expect(now.valueOf()).toBe(mockDate.valueOf())
const nowInstant = Temporal.Now.instant()
expect(nowInstant.epochMilliseconds).toBe(mockDate.valueOf())
// reset mocked time
vi.useRealTimers()

전역 변수 모킹하기

globalThis 에 값을 할당하거나 vi.stubGlobal 헬퍼를 써서 전역 변수를 설정할 수 있어요. vi.stubGlobal 을 쓸 때는 unstubGlobals 설정 옵션을 켜거나 vi.unstubAllGlobals 를 호출하지 않으면 다른 테스트 사이에 자동 리셋되지 아요.

vi.stubGlobal('__VERSION__', '1.0.0')
expect(__VERSION__).toBe('1.0.0')

import.meta.env 모킹하기

  1. 환경 변수를 바꾸려면 그냥 새 값을 할당하면 돼요.

::: warning 환경 변수 값은 다른 테스트 사이에 자동으로 리셋되지 아요. :::

import { beforeEach, expect, it } from 'vitest'

// you can reset it in beforeEach hook manually
const originalViteEnv = import.meta.env.VITE_ENV

beforeEach(() => {
  import.meta.env.VITE_ENV = originalViteEnv
})

it('changes value', () => {
  import.meta.env.VITE_ENV = 'staging'
  expect(import.meta.env.VITE_ENV).toBe('staging')
})
  1. 값을 자동으로 리셋하고 싶다면 unstubEnvs 설정 옵션을 켠 상태로 vi.stubEnv 헬퍼를 쓰면 됩니다(beforeEach 훅에서 vi.unstubAllEnvs 를 직접 호출해도 되고요):
import { expect, it, vi } from 'vitest'

// before running tests "VITE_ENV" is "test"
import.meta.env.VITE_ENV === 'test'

it('changes value', () => {
  vi.stubEnv('VITE_ENV', 'staging')
  expect(import.meta.env.VITE_ENV).toBe('staging')
})

it('the value is restored before running an other test', () => {
  expect(import.meta.env.VITE_ENV).toBe('test')
})
export default defineConfig({
  test: {
    unstubEnvs: true,
  },
})

더 알아보기 (Learn more)