Skip to content
Vitest 0
Main Navigation РуководствоAPIКонфигурацияПродвинутый
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

Режим браузера (экспериментальный)

In-source тестирование (Тестирование в исходном коде)

Контекст теста

Тестовая среда

Расширение проверок (matchers)

Интеграции с IDE

Отладка

Сравнения с другими тестовыми фреймворками

Руководство по миграции

Распространенные ошибки

API

Справочник по Test API

Mock-функции

Vi

expect

expectTypeOf

assertType

Конфигурация

Настройка Vitest

Содержание страницы

Мокирование ​

При написании тестов часто возникает необходимость в создании имитации (мока) внутреннего или внешнего сервиса. Это называется мокированием. Vitest предоставляет набор вспомогательных функций для упрощения мокирования, доступных через хелпер vi. Вы можете import { vi } from 'vitest' или получить к нему доступ глобально (если глобальная конфигурация включена).

WARNING

Всегда помните о необходимости очистки или восстановления моков до или после каждого запуска теста, чтобы отменить изменения состояния моков между запусками! Смотрите документацию 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(() => {
    // tell vitest we use mocked time
    vi.useFakeTimers();
  });

  afterEach(() => {
    // restoring date after each test run
    vi.useRealTimers();
  });

  it('allows purchases within business hours', () => {
    // set hour within business hours
    const date = new Date(2000, 1, 1, 13);
    vi.setSystemTime(date);

    // access Date.now() will result in the date set above
    expect(purchase()).toEqual({ message: 'Success' });
  });

  it('disallows purchases outside of business hours', () => {
    // set hour outside business hours
    const date = new Date(2000, 1, 1, 19);
    vi.setSystemTime(date);

    // access Date.now() will result in the date set above
    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, // can also be a `getter or setter if supported`
};

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's Mock Functions

Глобальные переменные ​

Вы можете мокировать глобальные переменные, отсутствующие в jsdom или node, используя хелпер vi.stubGlobal. Он добавит значение глобальной переменной в объект 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);

// now you can access it as `IntersectionObserver` or `window.IntersectionObserver`

Модули ​

Мокирование модулей позволяет отслеживать сторонние библиотеки, используемые в коде, и тестировать передаваемые аргументы, возвращаемые значения или даже переопределять их поведение.

Смотрите раздел API vi.mock() api section для более подробного описания API.

Алгоритм автоматического мокирования ​

Если импортируется модуль, для которого не определены файлы __mocks__ или factory, Vitest автоматически создаст мок для этого модуля, имитируя его вызов и мокируя каждый экспорт.

Применяются следующие принципы:

  • Все массивы будут очищены
  • Все примитивы и коллекции останутся прежними
  • Все объекты будут глубоко клонированы
  • Все экземпляры классов и их прототипы будут глубоко клонированы

Пример ​

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.js, мокирование сетевых запросов может быть затруднительным из-за отсутствия веб-API. Поэтому требуется инструмент, имитирующий сетевое поведение. Мы рекомендуем Mock Service Worker для этой цели. Он позволит вам мокировать как REST, так и GraphQL сетевые запросы и является фреймворк-агностиком.

Mock Service Worker (MSW) работает путем перехвата запросов, которые делают ваши тесты, позволяя вам использовать его без изменения кода вашего приложения. В браузере он использует Service Worker API. В Node.js и для Vitest он использует node-request-interceptor. Чтобы узнать больше о MSW, прочитайте их введение.

Конфигурация ​

Пример использования в файле конфигурации:

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

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

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

const graphqlHandlers = [
  graphql.query(
    'https://graphql-endpoint.example/api/v1/posts',
    (req, res, ctx) => {
      return res(ctx.data(posts));
    }
  ),
];

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

// Start server before all tests
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));

//  Close server after all tests
afterAll(() => server.close());

// Reset handlers after each test `important for test isolation`
afterEach(() => server.resetHandlers());

Конфигурация сервера с опцией onUnhandleRequest: 'error' гарантирует генерацию ошибки при каждом запросе, для которого не определен обработчик.

Пример ​

У нас есть полный рабочий пример, который использует MSW: React Testing with MSW.

Больше информации ​

В MSW есть гораздо больше возможностей. Вы можете получить доступ к файлам cookie и параметрам запроса, определять фиктивные ответы об ошибках и многое другое! Чтобы увидеть все, что вы можете делать с MSW, прочитайте их документацию.

Таймеры ​

При тестировании кода, использующего тайм-ауты или интервалы, вместо ожидания или истечения времени ожидания, можно ускорить тесты, используя имитацию таймеров, перехватывающую вызовы setTimeout и setInterval.

Смотрите раздел API vi.useFakeTimers api section для более подробного описания 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);
    // advancing by 2ms won't trigger the func
    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);
  });
});

Шпаргалка ​

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');
  • Мокировать экспортированную функцию

Пример с vi.mock:

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

vi.mock('./some-path.js', () => ({
  method: vi.fn(),
}));

WARNING

Помните, что вызов vi.mock перемещается в начало файла (hoisted). Не размещайте вызовы vi.mock внутри beforeEach, так как только один из них фактически замокает модуль.

Пример с vi.spyOn:

ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'method').mockImplementation(() => {});
  • Мокировать реализацию экспортированного класса

Пример с 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

Пример с 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.results будет содержать возвращенные объекты

Пример с vi.spyOn:

ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'SomeClass').mockImplementation(() => {
  // whatever suites you from first two examples
});
  • Шпионить за объектом, возвращаемым из функции

Пример с использованием кэша:

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 () => {
  const mod = await vi.importActual<typeof import('./some-path.js')>(
    './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

Чтобы изменить переменную окружения, вы можете просто присвоить ей новое значение. Это значение не будет автоматически сброшено между тестами.

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');
});

Если вы хотите автоматически сбросить значение, вы можете использовать функцию vi.stubEnv с включенным параметром конфигурации unstubEnvs (или вызвать vi.unstubAllEnvs вручную в хуке beforeEach):

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.

Авторские права (c) 2024 Mithril Contributors

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

Выпущено на условиях лицензии MIT.

Авторские права (c) 2024 Mithril Contributors