Skip to content
Vitest 1
Main Navigation 가이드API구성고급
3.2.0
2.1.9
1.6.1
0.34.6

한국어

English
简体中文
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
Italiano
Polski
Türkçe
čeština
magyar

한국어

English
简体中文
繁體中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
Italiano
Polski
Türkçe
čeština
magyar

외관

Sidebar Navigation

Vitest를 선택하는 이유

시작하기

기능

워크스페이스

명령줄 인터페이스

테스트 필터링

리포터

커버리지

스냅샷

모의화

타입 테스트

Vitest UI

브라우저 모드

소스 내 테스트

테스트 컨텍스트

테스트 환경

Matcher 확장하기

IDE 통합

디버깅

다른 테스트 러너와의 비교

마이그레이션 가이드

일반적인 오류

성능 향상

이 페이지에서

모의화 ​

테스트를 작성하다 보면 내부 또는 외부 서비스의 "가짜" 버전을 만들어야 할 때가 있습니다. 이를 일반적으로 **모의화(mocking)**라고 합니다. Vitest는 vi 도우미를 통해 이를 돕는 유틸리티 함수를 제공합니다. import { vi } from 'vitest'를 사용하여 가져오거나, 전역으로 접근할 수 있습니다 ( 전역 구성이 활성화된 경우).

WARNING

각 테스트 실행 전후에 모의화(mock)를 항상 초기화하거나 복원하여 테스트 간의 모의화 상태 변경을 방지해야 합니다! 자세한 내용은 mockReset 문서를 참조하십시오.

빠르게 시작하려면 API 섹션을 확인하고, 모의화에 대해 자세히 알아보세요.

날짜 ​

테스트 시 일관성을 유지하기 위해 날짜를 제어해야 할 경우가 있습니다. Vitest는 타이머와 시스템 날짜를 조작하기 위해 @sinonjs/fake-timers 패키지를 사용합니다. 특정 API에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

예시 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const businessHours = [9, 17];

function purchase() {
  const currentHour = new Date().getHours();
  const [open, close] = businessHours;

  if (currentHour > open && currentHour < close) return { message: 'Success' };

  return { message: 'Error' };
}

describe('purchasing flow', () => {
  beforeEach(() => {
    // vitest에게 모의 시간 사용을 알립니다.
    vi.useFakeTimers();
  });

  afterEach(() => {
    // 각 테스트 실행 후 날짜를 복원합니다.
    vi.useRealTimers();
  });

  it('allows purchases within business hours', () => {
    // 영업 시간 내로 시간을 설정합니다.
    const date = new Date(2000, 1, 1, 13);
    vi.setSystemTime(date);

    // Date.now()에 접근하면 위에서 설정한 날짜가 반환됩니다.
    expect(purchase()).toEqual({ message: 'Success' });
  });

  it('disallows purchases outside of business hours', () => {
    // 영업 시간 외로 시간을 설정합니다.
    const date = new Date(2000, 1, 1, 19);
    vi.setSystemTime(date);

    // Date.now()에 접근하면 위에서 설정한 날짜가 반환됩니다.
    expect(purchase()).toEqual({ message: 'Error' });
  });
});

함수 ​

함수 모의화는 스파이와 모의 함수, 두 가지 범주로 나눌 수 있습니다.

특정 함수가 호출되었는지 (그리고 어떤 인수가 전달되었는지) 확인하기만 하면 되는 경우가 있습니다. 이러한 경우 스파이만으로 충분하며, vi.spyOn()을 사용하여 직접 생성할 수 있습니다 (자세한 내용은 여기 참조).

그러나 스파이는 함수를 감시하는 데만 사용되며, 해당 함수의 구현을 변경할 수는 없습니다. 함수의 가짜 (또는 모의화된) 버전을 만들어야 하는 경우 vi.fn()을 사용할 수 있습니다 (자세한 내용은 여기 참조).

함수 모의화의 기반으로 Tinyspy를 사용하지만, jest와 호환성을 위해 자체 래퍼를 가지고 있습니다. vi.fn()과 vi.spyOn()은 동일한 메서드를 공유하지만, vi.fn()의 반환 값만 호출할 수 있습니다.

예시 ​

js
import { afterEach, describe, expect, it, vi } from 'vitest';

function getLatest(index = messages.items.length - 1) {
  return messages.items[index];
}

const messages = {
  items: [
    { message: 'Simple test message', from: 'Testman' },
    // ...
  ],
  getLatest, // getter 또는 setter도 지원됩니다.
};

describe('reading messages', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should get the latest message with a spy', () => {
    const spy = vi.spyOn(messages, 'getLatest');
    expect(spy.getMockName()).toEqual('getLatest');

    expect(messages.getLatest()).toEqual(
      messages.items[messages.items.length - 1]
    );

    expect(spy).toHaveBeenCalledTimes(1);

    spy.mockImplementationOnce(() => 'access-restricted');
    expect(messages.getLatest()).toEqual('access-restricted');

    expect(spy).toHaveBeenCalledTimes(2);
  });

  it('should get with a mock', () => {
    const mock = vi.fn().mockImplementation(getLatest);

    expect(mock()).toEqual(messages.items[messages.items.length - 1]);
    expect(mock).toHaveBeenCalledTimes(1);

    mock.mockImplementationOnce(() => 'access-restricted');
    expect(mock()).toEqual('access-restricted');

    expect(mock).toHaveBeenCalledTimes(2);

    expect(mock()).toEqual(messages.items[messages.items.length - 1]);
    expect(mock).toHaveBeenCalledTimes(3);
  });
});

더 보기 ​

  • Jest의 Mock Functions

전역 변수 ​

vi.stubGlobal 헬퍼를 사용하여 jsdom 또는 node에 없는 전역 변수를 모의화할 수 있습니다. 전역 변수의 값을 globalThis 객체에 할당합니다.

ts
import { vi } from 'vitest';

const IntersectionObserverMock = vi.fn(() => ({
  disconnect: vi.fn(),
  observe: vi.fn(),
  takeRecords: vi.fn(),
  unobserve: vi.fn(),
}));

vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);

// 이제 `IntersectionObserver` 또는 `window.IntersectionObserver`로 접근할 수 있습니다.

모듈 ​

모듈 모의화는 다른 코드에서 호출되는 타사 라이브러리를 감시하여 인수, 출력 또는 구현을 재정의할 수 있도록 합니다.

자세한 API 설명은 vi.mock() API 섹션을 참조하십시오.

자동 모의화 알고리즘 ​

코드에서 연결된 __mocks__ 파일이나 해당 모듈에 대한 factory 없이 모의화된 모듈을 가져오는 경우, Vitest는 모듈을 호출하고 모든 내보내기를 모의화하여 모듈 자체를 자동으로 모의화합니다.

다음 원칙이 적용됩니다.

  • 모든 배열이 비워집니다.
  • 모든 원시 타입과 컬렉션은 동일하게 유지됩니다.
  • 모든 객체는 깊은 복사됩니다.
  • 클래스의 모든 인스턴스와 해당 프로토타입은 깊은 복사됩니다.

가상 모듈 ​

Vitest는 Vite 가상 모듈 mocking을 지원합니다. 이는 Jest에서 가상 모듈을 처리하는 방식과는 다릅니다. vi.mock 함수에 virtual: true를 전달하는 대신, Vite에게 해당 모듈이 존재한다는 것을 알려줘야 합니다. 그러지 않으면 파싱 중에 실패합니다. 이를 수행하는 몇 가지 방법이 있습니다.

  1. 별칭 제공
ts
// vitest.config.js
export default {
  test: {
    alias: {
      '$app/forms': resolve('./mocks/forms.js'),
    },
  },
};
  1. 가상 모듈을 resolve하는 플러그인 제공
ts
// vitest.config.js
export default {
  plugins: [
    {
      name: 'virtual-modules',
      resolveId(id) {
        if (id === '$app/forms') return 'virtual:$app/forms';
      },
    },
  ],
};

두 번째 접근 방식의 장점은 다양한 가상 진입점을 동적으로 생성할 수 있다는 것입니다. 여러 가상 모듈을 단일 파일로 리디렉션하면 모두 vi.mock의 영향을 받으므로 고유 식별자를 사용해야 합니다.

Mocking의 함정 ​

동일한 파일의 다른 메서드 내에서 호출되는 메서드에 대한 호출을 mocking하는 것은 불가능하다는 점에 유의하세요. 예를 들어 다음 코드에서:

ts
export function foo() {
  return 'foo';
}

export function foobar() {
  return `${foo()}bar`;
}

foo 메서드가 직접 참조되기 때문에 외부에서 foo 메서드를 mocking하는 것은 불가능합니다. 따라서 이 코드는 foobar 내의 foo 호출에는 아무런 영향을 미치지 않습니다 (다른 모듈의 foo 호출에는 영향을 미치지만).

ts
import { vi } from 'vitest';
import * as mod from './foobar.js';

// 이는 원래 모듈 외부의 "foo"에만 영향을 미칩니다.
vi.spyOn(mod, 'foo');
vi.mock('./foobar.js', async importOriginal => {
  return {
    ...(await importOriginal<typeof import('./foobar.js')>()),
    // 이는 원래 모듈 외부의 "foo"에만 영향을 미칩니다.
    foo: () => 'mocked',
  };
});

구현을 foobar 메서드에 직접 제공하여 이 동작을 확인할 수 있습니다.

ts
// foobar.test.js
import * as mod from './foobar.js';

vi.spyOn(mod, 'foo');

// 내보내진 foo는 mocking된 메서드를 참조합니다.
mod.foobar(mod.foo);
ts
// foobar.js
export function foo() {
  return 'foo';
}

export function foobar(injectedFoo) {
  return injectedFoo !== foo; // false
}

이것은 의도된 동작입니다. mocking이 그러한 방식으로 관여될 때는 일반적으로 잘못된 코드의 신호입니다. 코드를 여러 파일로 리팩터링하거나 의존성 주입과 같은 기술을 사용하여 애플리케이션 아키텍처를 개선하는 것을 고려해 보세요.

예시 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Client } from 'pg';
import { failure, success } from './handlers.js';

// handlers
export function success(data) {}
export function failure(data) {}

// get todos
export async function getTodos(event, context) {
  const client = new Client({
    // ...clientOptions
  });

  await client.connect();

  try {
    const result = await client.query('SELECT * FROM todos;');

    client.end();

    return success({
      message: `${result.rowCount} item(s) returned`,
      data: result.rows,
      status: true,
    });
  } catch (e) {
    console.error(e.stack);

    client.end();

    return failure({ message: e, status: false });
  }
}

vi.mock('pg', () => {
  const Client = vi.fn();
  Client.prototype.connect = vi.fn();
  Client.prototype.query = vi.fn();
  Client.prototype.end = vi.fn();

  return { Client };
});

vi.mock('./handlers.js', () => {
  return {
    success: vi.fn(),
    failure: vi.fn(),
  };
});

describe('get a list of todo items', () => {
  let client;

  beforeEach(() => {
    client = new Client();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it('should return items successfully', async () => {
    client.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });

    await getTodos();

    expect(client.connect).toBeCalledTimes(1);
    expect(client.query).toBeCalledWith('SELECT * FROM todos;');
    expect(client.end).toBeCalledTimes(1);

    expect(success).toBeCalledWith({
      message: '0 item(s) returned',
      data: [],
      status: true,
    });
  });

  it('should throw an error', async () => {
    const mError = new Error('Unable to retrieve rows');
    client.query.mockRejectedValueOnce(mError);

    await getTodos();

    expect(client.connect).toBeCalledTimes(1);
    expect(client.query).toBeCalledWith('SELECT * FROM todos;');
    expect(client.end).toBeCalledTimes(1);
    expect(failure).toBeCalledWith({ message: mError, status: false });
  });
});

요청 ​

Vitest는 Node 환경에서 실행되므로 네트워크 요청을 모의화하기가 어려울 수 있습니다. 웹 API를 사용할 수 없으므로 네트워크 동작을 모방할 수 있는 도구가 필요합니다. 이러한 이유로 Mock Service Worker를 사용하는 것을 권장합니다. 이를 통해 REST 및 GraphQL 네트워크 요청을 모두 모의화할 수 있으며, 특정 프레임워크에 종속되지 않습니다.

Mock Service Worker (MSW)는 테스트 과정에서 발생하는 요청을 가로채 애플리케이션 코드 수정 없이 모의화 기능을 제공합니다. 브라우저에서는 Service Worker API를 사용하고, Node.js 및 Vitest 환경에서는 @mswjs/interceptors를 활용합니다. MSW에 대한 자세한 내용은 소개를 참조하십시오.

구성 ​

설정 파일에서 다음과 같이 설정하여 사용할 수 있습니다.

js
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { HttpResponse, graphql, http } from 'msw';

const posts = [
  {
    userId: 1,
    id: 1,
    title: 'first post title',
    body: 'first post body',
  },
  // ...
];

export const restHandlers = [
  http.get('https://rest-endpoint.example/path/to/posts', () => {
    return HttpResponse.json(posts);
  }),
];

const graphqlHandlers = [
  graphql.query('ListPosts', () => {
    return HttpResponse.json({
      data: { posts },
    });
  }),
];

const server = setupServer(...restHandlers, ...graphqlHandlers);

// 모든 테스트 전에 서버를 시작합니다.
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

// 모든 테스트 후에 서버를 닫습니다.
afterAll(() => server.close());

// 각 테스트 후 핸들러를 초기화합니다. (테스트 격리를 위해 중요합니다.)
afterEach(() => server.resetHandlers());

onUnhandleRequest: 'error' 설정을 통해 서버에 정의되지 않은 요청이 발생할 경우 오류를 발생시킬 수 있습니다.

예시 ​

MSW를 활용한 완전한 예제는 다음 링크에서 확인할 수 있습니다. MSW를 사용한 React 테스팅.

더 보기 ​

MSW에는 훨씬 더 많은 기능이 있습니다. 쿠키, 쿼리 매개변수 접근, 모의 오류 응답 정의 등 다양한 기능을 제공합니다! MSW로 할 수 있는 모든 것을 보려면 해당 문서를 참조하십시오.

타이머 ​

시간 제한 또는 간격이 포함된 코드를 테스트할 때, 테스트가 완료될 때까지 기다리거나 시간 초과를 발생시키는 대신, setTimeout 및 setInterval 호출을 모의화하는 '가짜' 타이머를 사용하여 테스트 속도를 향상시킬 수 있습니다.

자세한 API 설명은 vi.useFakeTimers API 섹션을 참조하십시오.

예시 ​

js
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

function executeAfterTwoHours(func) {
  setTimeout(func, 1000 * 60 * 60 * 2); // 2 hours
}

function executeEveryMinute(func) {
  setInterval(func, 1000 * 60); // 1 minute
}

const mock = vi.fn(() => console.log('executed'));

describe('delayed execution', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });
  afterEach(() => {
    vi.restoreAllMocks();
  });
  it('should execute the function', () => {
    executeAfterTwoHours(mock);
    vi.runAllTimers();
    expect(mock).toHaveBeenCalledTimes(1);
  });
  it('should not execute the function', () => {
    executeAfterTwoHours(mock);
    // 2ms만큼 시간을 진행해도 함수가 실행되지 않습니다.
    vi.advanceTimersByTime(2);
    expect(mock).not.toHaveBeenCalled();
  });
  it('should execute every minute', () => {
    executeEveryMinute(mock);
    vi.advanceTimersToNextTimer();
    expect(mock).toHaveBeenCalledTimes(1);
    vi.advanceTimersToNextTimer();
    expect(mock).toHaveBeenCalledTimes(2);
  });
});

Cheat Sheet ​

INFO

아래 예제의 vi는 vitest에서 직접 가져온 것입니다. 구성에서 globals를 true로 설정하면 전역으로 사용할 수도 있습니다.

다음을 수행하고 싶습니다…

method 스파이하기 ​

ts
const instance = new SomeClass();
vi.spyOn(instance, 'method');

내보낸 변수 모킹하기 ​

js
// some-path.js
export const getter = 'variable';
ts
// some-path.test.ts
import * as exports from './some-path.js';

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

내보낸 함수 모킹하기 ​

  1. vi.mock을 사용한 예시:

WARNING

vi.mock 호출은 파일 맨 위로 호이스팅됩니다. 항상 모든 import 문보다 먼저 실행됩니다.

ts
// ./some-path.js
export function method() {}
ts
import { method } from './some-path.js';

vi.mock('./some-path.js', () => ({
  method: vi.fn(),
}));
  1. vi.spyOn을 사용한 예:
ts
import * as exports from './some-path.js';

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

내보낸 클래스 구현 모킹하기 ​

  1. vi.mock 및 .prototype을 사용한 예시:
ts
// some-path.ts
export class SomeClass {}
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn();
  SomeClass.prototype.someMethod = vi.fn();
  return { SomeClass };
});
// SomeClass.mock.instances는 SomeClass의 인스턴스를 갖습니다.
  1. vi.mock 및 반환 값을 사용한 예:
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn(() => ({
    someMethod: vi.fn(),
  }));
  return { SomeClass };
});
// SomeClass.mock.returns는 반환된 객체들을 갖습니다.
  1. vi.spyOn을 사용한 예:
ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'SomeClass').mockImplementation(() => {
  // 처음 두 예제 중 원하는 방식으로 구현할 수 있습니다.
});

함수에서 반환된 객체 스파이하기 ​

  1. 캐시를 사용한 예:
ts
// some-path.ts
export function useObject() {
  return { method: () => true };
}
ts
// useObject.js
import { useObject } from './some-path.js';

const obj = useObject();
obj.method();
ts
// useObject.test.js
import { useObject } from './some-path.js';

vi.mock('./some-path.js', () => {
  let _cache;
  const useObject = () => {
    if (!_cache) {
      _cache = {
        method: vi.fn(),
      };
    }
    // 이제 useObject()가 호출될 때마다
    // 동일한 객체 레퍼런스를 반환합니다.
    return _cache;
  };
  return { useObject };
});

const obj = useObject();
// obj.method는 some-path 내부에서 호출되었습니다.
expect(obj.method).toHaveBeenCalled();

모듈의 일부만 모킹하기 ​

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

vi.mock('./some-path.js', async importOriginal => {
  const mod = await importOriginal<typeof import('./some-path.js')>();
  return {
    ...mod,
    mocked: vi.fn(),
  };
});
original(); // 원본 동작을 유지합니다.
mocked(); // 스파이 함수로 모킹되었습니다.

현재 날짜 모킹하기 ​

Date의 시간을 모의하려면 vi.setSystemTime 도우미 함수를 사용할 수 있습니다. 이 값은 테스트 간에 자동으로 재설정되지 않습니다.

vi.useFakeTimers를 사용하면 Date의 시간도 변경된다는 점에 유의하세요.

ts
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// 모킹된 시간 초기화
vi.useRealTimers();

전역 변수 모킹하기 ​

globalThis에 값을 할당하거나 vi.stubGlobal 도우미를 사용하여 전역 변수를 설정할 수 있습니다. vi.stubGlobal을 사용하는 경우 unstubGlobals 구성 옵션을 활성화하거나 vi.unstubAllGlobals를 호출하지 않는 한 테스트 간에 자동으로 재설정되지 않습니다.

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

import.meta.env 모킹하기 ​

  1. 환경 변수를 변경하려면 새 값을 할당하기만 하면 됩니다.

WARNING

환경 변수 값은 서로 다른 테스트 간에 자동으로 재설정**되지 않습니다**.

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

// beforeEach 훅에서 수동으로 초기화할 수 있습니다.
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](/api/vi#vi-unstuballenvs)를 수동으로 호출할 수 있습니다.
ts
import { expect, it, vi } from 'vitest';

// 테스트 실행 전 "VITE_ENV"는 "test"입니다.
import.meta.env.VITE_ENV === 'test';

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

it('다른 테스트를 실행하기 전에 값이 복원됩니다.', () => {
  expect(import.meta.env.VITE_ENV).toBe('test');
});
ts
// vitest.config.ts
export default {
  test: {
    unstubAllEnvs: true,
  },
};
Pager
이전스냅샷
다음타입 테스트

MIT 라이선스 하에 배포되었습니다.

Copyright (c) 2021-Present Vitest Team

https://v1.vitest.dev/guide/mocking

MIT 라이선스 하에 배포되었습니다.

Copyright (c) 2021-Present Vitest Team