Skip to content
Vitest 1
Main Navigation PrzewodnikAPIKonfiguracjaZaawansowany
1.6.1
0.34.6

Polski

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

Polski

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

Wygląd

Sidebar Navigation

Przewodnik

Dlaczego Vitest

Wprowadzenie

Funkcje

Przestrzeń robocza

Interfejs Linii Poleceń

Filtrowanie Testów

Reportery

Pokrycie kodu

Snapshot

Mockowanie

Testowanie typów

Interfejs użytkownika Vitest

Tryb przeglądarki

Testowanie w kodzie źródłowym

Kontekst Testowy

Środowisko Testowe

Rozszerzanie Matcherów

Integracje z IDE

Debugowanie

Porównania z innymi narzędziami do uruchamiania testów

Przewodnik migracji

Częste błędy

Poprawa wydajności

API

Dokumentacja API Testów

Funkcje Mockujące

Vi

expect

expectTypeOf

assert

assertType

Konfiguracja

Zarządzanie plikiem konfiguracyjnym Vitest

Konfiguracja Vitest

Na tej stronie

Mockowanie ​

Podczas pisania testów prędzej czy później pojawi się potrzeba stworzenia "fałszywej" wersji zależności wewnętrznej lub zewnętrznej. Jest to powszechnie nazywane mockowaniem. Vitest udostępnia funkcje pomocnicze, które ułatwiają ten proces za pomocą obiektu vi. Możesz użyć import { vi } from 'vitest' lub uzyskać do niego dostęp globalnie (gdy konfiguracja globalna jest włączona).

WARNING

Zawsze pamiętaj, aby wyczyścić lub przywrócić mocki przed lub po każdym teście, aby uniknąć niepożądanych zmian stanu między testami! Więcej informacji znajdziesz w dokumentacji mockReset.

Jeśli chcesz od razu przejść do konkretnych rozwiązań, sprawdź sekcję API; w przeciwnym razie kontynuuj czytanie, aby dowiedzieć się więcej o mockowaniu.

Daty ​

Czasami konieczne jest kontrolowanie daty, aby zapewnić spójność testów. Vitest używa pakietu @sinonjs/fake-timers do manipulowania czasomierzami, a także datą systemową. Szczegółowe informacje na temat konkretnego API można znaleźć tutaj.

Przykład ​

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

Funkcje ​

Mockowanie funkcji dzieli się na dwie kategorie: szpiegowanie (ang. spying) i mockowanie (ang. mocking).

Czasami wystarczy sprawdzić, czy dana funkcja została wywołana (i ewentualnie z jakimi argumentami). W takich przypadkach szpiegowanie jest wystarczające i można je zrealizować bezpośrednio za pomocą vi.spyOn() (więcej informacji tutaj).

Jednak szpiedzy (ang. spies) służą tylko do obserwowania funkcji, nie mogą zmieniać ich implementacji. Jeśli potrzebujemy utworzyć fałszywą (lub mockowaną) wersję funkcji, możemy użyć vi.fn() (więcej informacji tutaj).

Używamy Tinyspy jako podstawy do mockowania funkcji, ale mamy własną otoczkę, aby zapewnić kompatybilność z jest. Zarówno vi.fn() i vi.spyOn() współdzielą te same metody, jednak tylko wynik zwracany przez vi.fn() jest wywoływalny.

Przykład ​

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

Więcej ​

  • Funkcje Mock w Jest

Zmienne globalne ​

Możesz mockować zmienne globalne, które nie są dostępne w jsdom lub node, używając pomocnika vi.stubGlobal. Umieści on wartość zmiennej globalnej w obiekcie 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`

Moduły ​

Mockowanie modułów pozwala obserwować biblioteki firm trzecich, które są wywoływane w kodzie, umożliwiając testowanie argumentów, wyników, a nawet ponowne definiowanie ich implementacji.

Zobacz sekcję API vi.mock(), aby uzyskać bardziej szczegółowy opis API.

Algorytm automatycznego mockowania ​

Jeśli twój kod importuje mockowany moduł, bez powiązanego pliku __mocks__ lub factory dla tego modułu, Vitest automatycznie zamockuje moduł, wywołując go i mockując każdy eksport.

Obowiązują następujące zasady:

  • Wszystkie tablice zostaną wyczyszczone.
  • Wszystkie typy proste (primitive) i kolekcje pozostaną bez zmian.
  • Wszystkie obiekty zostaną głęboko sklonowane.
  • Wszystkie instancje klas i ich prototypy zostaną głęboko sklonowane.

Moduły wirtualne ​

Vitest obsługuje mockowanie wirtualnych modułów Vite (https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention). Działa to inaczej niż w przypadku modułów wirtualnych w Jest. Zamiast przekazywać virtual: true do funkcji vi.mock, należy poinformować Vite, że moduł istnieje, w przeciwnym razie parsowanie zakończy się niepowodzeniem. Można to zrobić na kilka sposobów:

  1. Dostarczenie aliasu
ts
// vitest.config.js
export default {
  test: {
    alias: {
      '$app/forms': resolve('./mocks/forms.js'),
    },
  },
};
  1. Dostarczenie wtyczki, która rozwiązuje moduł wirtualny
ts
// vitest.config.js
export default {
  plugins: [
    {
      name: 'virtual-modules',
      resolveId(id) {
        if (id === '$app/forms') return 'virtual:$app/forms';
      },
    },
  ],
};

Zaletą drugiego podejścia jest możliwość dynamicznego tworzenia różnych wirtualnych punktów wejścia. Jeśli przekierujesz kilka modułów wirtualnych do jednego pliku, wszystkie zostaną dotknięte przez vi.mock, dlatego upewnij się, że używasz unikalnych identyfikatorów.

Pułapki mockowania ​

Należy pamiętać, że nie można mockować wywołań metod, które są wywoływane wewnątrz innych metod tego samego pliku. Na przykład w tym kodzie:

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

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

Nie można mockować metody foo z zewnątrz, ponieważ jest do niej bezpośrednie odwołanie. Zatem ten kod nie będzie miał wpływu na wywołanie foo wewnątrz foobar (ale wpłynie na wywołanie foo w innych modułach):

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

// to wpłynie tylko na "foo" poza oryginalnym modułem
vi.spyOn(mod, 'foo');
vi.mock('./foobar.js', async importOriginal => {
  return {
    ...(await importOriginal<typeof import('./foobar.js')>()),
    // to wpłynie tylko na "foo" poza oryginalnym modułem
    foo: () => 'mocked',
  };
});

Można potwierdzić to zachowanie, dostarczając implementację bezpośrednio do metody foobar:

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

vi.spyOn(mod, 'foo');

// eksportowane foo odwołuje się do mockowanej metody
mod.foobar(mod.foo);
ts
// foobar.js
export function foo() {
  return 'foo';
}

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

Takie jest zamierzone zachowanie. Zwykle jest to oznaka złego kodu, gdy mockowanie jest zaangażowane w taki sposób. Rozważ refaktoryzację kodu na wiele plików lub ulepszenie architektury aplikacji przy użyciu technik takich jak wstrzykiwanie zależności.

Przykład ​

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} element(ów) zwróconych`,
      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 });
  });
});

Żądania ​

Ponieważ Vitest działa w Node.js, mockowanie żądań sieciowych jest utrudnione; interfejsy API przeglądarki nie są dostępne, więc potrzebujemy czegoś, co będzie naśladować zachowanie sieci. Zalecamy Mock Service Worker do tego celu. Pozwoli to na mockowanie zarówno żądań sieciowych REST, jak i GraphQL i jest niezależne od frameworka.

Mock Service Worker (MSW) działa poprzez przechwytywanie żądań wykonywanych przez testy, co pozwala na używanie go bez zmiany kodu aplikacji. W przeglądarce używa Service Worker API. W Node.js i dla Vitest używa @mswjs/interceptors. Aby dowiedzieć się więcej o MSW, przeczytaj wprowadzenie

Konfiguracja ​

Możesz go użyć w swoim pliku konfiguracyjnym w następujący sposób:

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

// 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());

Skonfigurowanie serwera z onUnhandleRequest: 'error' zapewnia, że zostanie zgłoszony błąd, gdy tylko pojawi się żądanie, dla którego nie zdefiniowano odpowiedniego handlera.

Przykład ​

Mamy w pełni działający przykład, który używa MSW: React Testing with MSW.

Więcej ​

MSW ma o wiele więcej do zaoferowania. Możesz uzyskać dostęp do plików cookie i parametrów zapytania, definiować mockowane odpowiedzi błędów i wiele więcej! Aby zobaczyć wszystko, co możesz zrobić z MSW, przeczytaj ich dokumentację.

Timery ​

Podczas testowania kodu zawierającego limity czasu lub interwały, zamiast czekać na zakończenie testów lub przekroczenie limitu czasu, możemy przyspieszyć nasze testy, używając mockowanych timerów, które mockują wywołania setTimeout i setInterval.

Zobacz sekcję API vi.useFakeTimers, aby uzyskać bardziej szczegółowy opis API.

Przykład ​

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

Podręczny zestaw ​

INFO

vi w poniższych przykładach jest importowane bezpośrednio z vitest. Możesz również używać go globalnie, jeśli ustawisz opcję globals na true w swojej konfiguracji.

Chcę…

Szpiegować metodę ​

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

Mockować eksportowane zmienne ​

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

Mockować eksportowaną funkcję ​

  1. Przykład z użyciem vi.mock:

WARNING

Pamiętaj, że wywołanie vi.mock jest przenoszone na początek pliku. Zawsze będzie wykonywane przed wszystkimi importami.

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

vi.mock('./some-path.js', () => ({
  method: vi.fn(),
}));
  1. Przykład z użyciem vi.spyOn:
ts
import * as exports from './some-path.js';

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

Mockować implementację eksportowanej klasy ​

  1. Przykład z użyciem vi.mock i .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 będzie zawierać instancje SomeClass
  1. Przykład z vi.mock i wartością zwracaną:
ts
import { SomeClass } from './some-path.js';

vi.mock('./some-path.js', () => {
  const SomeClass = vi.fn(() => ({
    someMethod: vi.fn(),
  }));
  return { SomeClass };
});
// SomeClass.mock.results będzie zawierać zwrócone obiekty
  1. Przykład z vi.spyOn:
ts
import * as exports from './some-path.js';

vi.spyOn(exports, 'SomeClass').mockImplementation(() => {
  // cokolwiek pasuje z dwóch pierwszych przykładów
});

Szpiegować obiekt zwracany z funkcji ​

  1. Przykład użycia cache:
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(),
      };
    }
    // teraz każde wywołanie useObject() zwróci tę samą referencję do obiektu
    return _cache;
  };
  return { useObject };
});

const obj = useObject();
// obj.method została wywołana wewnątrz some-path
expect(obj.method).toHaveBeenCalled();

Mockować część modułu ​

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(); // ma oryginalne zachowanie
mocked(); // jest funkcją szpiegującą

Mockować aktualną datę ​

Aby zamockować czas Date, możesz użyć funkcji pomocniczej vi.setSystemTime. Ta wartość nie jest automatycznie resetowana między testami.

Pamiętaj, że użycie vi.useFakeTimers również wpływa na czas Date.

ts
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// resetowanie zamockowanego czasu
vi.useRealTimers();

Mockować zmienną globalną ​

Możesz ustawić zmienną globalną, przypisując wartość do globalThis lub używając funkcji pomocniczej vi.stubGlobal. Używając vi.stubGlobal, wartość nie jest automatycznie resetowana między testami, chyba że włączysz opcję konfiguracji unstubGlobals lub wywołasz vi.unstubAllGlobals.

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

Mockować import.meta.env ​

  1. Aby zmienić zmienną środowiskową, możesz po prostu przypisać jej nową wartość.

WARNING

Wartość zmiennej środowiskowej nie zostanie automatycznie zresetowana między różnymi testami.

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

// możesz zresetować ją ręcznie w hooku beforeEach
const originalViteEnv = import.meta.env.VITE_ENV;

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

it('zmienia wartość', () => {
  import.meta.env.VITE_ENV = 'staging';
  expect(import.meta.env.VITE_ENV).toBe('staging');
});
  1. Jeśli chcesz automatycznie zresetować wartości, możesz użyć pomocnika vi.stubEnv z włączoną opcją konfiguracji unstubEnvs (lub wywołać ręcznie vi.unstubAllEnvs](/api/vi#vi-unstuballenvs) w hooku beforeEach):
ts
import { expect, it, vi } from 'vitest';

// przed uruchomieniem testów "VITE_ENV" ma wartość "test"
import.meta.env.VITE_ENV === 'test';

it('zmienia wartość', () => {
  vi.stubEnv('VITE_ENV', 'staging');
  expect(import.meta.env.VITE_ENV).toBe('staging');
});

it('wartość jest przywracana przed uruchomieniem kolejnego testu', () => {
  expect(import.meta.env.VITE_ENV).toBe('test');
});
ts
// vitest.config.ts
export default {
  test: {
    unstubAllEnvs: true,
  },
};
Pager
Poprzednia stronaSnapshot
Następna stronaTestowanie typów

Opublikowano na licencji MIT.

Copyright (c) 2024 Mithril Contributors

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

Opublikowano na licencji MIT.

Copyright (c) 2024 Mithril Contributors