Mockowanie
Podczas pisania testów prędzej czy później pojawi się potrzeba stworzenia „fałszywej” wersji wewnętrznej lub zewnętrznej usługi. Jest to powszechnie określane jako mockowanie. Vitest udostępnia funkcje pomocnicze, które wspierają ten proces za pośrednictwem pomocnika vi
. Możesz go zaimportować z vitest
lub używać globalnie, jeśli konfiguracja global
jest włączona.
WARNING
Zawsze pamiętaj o czyszczeniu lub przywracaniu mocków przed lub po każdym teście, aby cofnąć zmiany stanu mocków między uruchomieniami! Więcej informacji znajdziesz w dokumentacji mockReset
.
Jeśli nie znasz metod vi.fn
, vi.mock
lub vi.spyOn
, najpierw zapoznaj się z sekcją API.
Daty
Czasami konieczne jest kontrolowanie daty, aby zapewnić spójność testów. Vitest używa pakietu @sinonjs/fake-timers
do manipulowania timerami oraz datą systemową. Więcej szczegółów na temat konkretnego API znajdziesz tutaj.
Przykład
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(() => {
// informujemy Vitest, że używamy zamockowanego czasu
vi.useFakeTimers();
});
afterEach(() => {
// przywracanie rzeczywistej daty po każdym uruchomieniu testu
vi.useRealTimers();
});
it('allows purchases within business hours', () => {
// ustaw godzinę w godzinach pracy
const date = new Date(2000, 1, 1, 13);
vi.setSystemTime(date);
// dostęp do Date.now() zwróci datę ustawioną powyżej
expect(purchase()).toEqual({ message: 'Success' });
});
it('disallows purchases outside of business hours', () => {
// ustaw godzinę poza godzinami pracy
const date = new Date(2000, 1, 1, 19);
vi.setSystemTime(date);
// dostęp do Date.now() zwróci datę ustawioną powyżej
expect(purchase()).toEqual({ message: 'Error' });
});
});
Funkcje
Mockowanie funkcji można podzielić na dwie kategorie: szpiegowanie i mockowanie.
Czasami wystarczy tylko sprawdzić, czy określona funkcja została wywołana i jakie argumenty zostały przekazane. W takich przypadkach wystarczy szpieg, którego można użyć bezpośrednio za pomocą vi.spyOn()
(czytaj więcej tutaj).
Jednak szpiedzy mogą jedynie monitorować funkcje, nie są w stanie zmieniać ich implementacji. W przypadku, gdy musimy stworzyć fałszywą (lub zamockowaną) wersję funkcji, możemy użyć vi.fn()
(czytaj więcej tutaj).
Używamy Tinyspy jako podstawy do mockowania funkcji, ale posiadamy własną otoczkę, która zapewnia kompatybilność z jest
. Zarówno vi.fn()
, jak i vi.spyOn()
współdzielą te same metody, jednak tylko vi.fn()
zwraca wywoływalny wynik.
Przykład
import { afterEach, describe, expect, it, vi } from 'vitest';
const messages = {
items: [
{ message: 'Simple test message', from: 'Testman' },
// ...
],
getLatest, // może być również `getterem lub setterem, jeśli obsługiwane`
};
function getLatest(index = messages.items.length - 1) {
return messages.items[index];
}
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
Zmienne globalne
Możesz mockować zmienne globalne, które nie są obecne w jsdom
lub node
, używając pomocnika vi.stubGlobal
. Umieści on wartość zmiennej globalnej w obiekcie globalThis
.
import { vi } from 'vitest';
const IntersectionObserverMock = vi.fn(() => ({
disconnect: vi.fn(),
observe: vi.fn(),
takeRecords: vi.fn(),
unobserve: vi.fn(),
}));
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
// teraz możesz uzyskać do niego dostęp jako `IntersectionObserver` lub `window.IntersectionObserver`
Moduły
Mockowanie modułów pozwala obserwować biblioteki stron trzecich, które są wywoływane w innym kodzie, umożliwiając testowanie argumentów, danych wyjściowych, a nawet ponowne definiowanie ich implementacji.
Szczegółowy opis API znajdziesz w sekcji vi.mock()
API.
Algorytm automatycznego mockowania
Jeśli Twój kod importuje zamockowany moduł, bez powiązanego pliku __mocks__
lub factory
dla tego modułu, Vitest samodzielnie zamockuje moduł, wywołując go i mockując wszystkie eksporty.
Obowiązują następujące zasady:
- Wszystkie tablice zostaną wyczyszczone.
- Wszystkie prymitywy i kolekcje pozostaną niezmienione.
- Wszystkie obiekty zostaną głęboko sklonowane.
- Wszystkie instancje klas i ich prototypy będą głęboko klonowane.
Moduły wirtualne
Vitest obsługuje mockowanie modułów wirtualnych Vite. Działa to inaczej niż sposób, w jaki moduły wirtualne są traktowane w Jest. Zamiast przekazywać virtual: true
do funkcji vi.mock
, musisz poinformować Vite o istnieniu modułu, w przeciwnym razie podczas parsowania wystąpi błąd. Możesz to zrobić na kilka sposobów:
- Podaj alias
import { defineConfig } from 'vitest/config';
import { resolve } from 'node:path';
export default defineConfig({
test: {
alias: {
'$app/forms': resolve('./mocks/forms.js'),
},
},
});
- Udostępnij wtyczkę, która rozwiązuje moduł wirtualny
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
{
name: 'virtual-modules',
resolveId(id) {
if (id === '$app/forms') {
return 'virtual:$app/forms';
}
},
},
],
});
Zaletą drugiego podejścia jest to, że możesz dynamicznie tworzyć różne wirtualne punkty wejścia. Jeśli przekierujesz kilka modułów wirtualnych do jednego pliku, wszystkie z nich będą podlegać vi.mock
, więc upewnij się, że używasz unikalnych identyfikatorów.
Pułapki mockowania
Pamiętaj, że nie jest możliwe mockowanie wywołań metod, które są wywoływane wewnątrz innych metod tego samego pliku. Na przykład w tym kodzie:
export function foo() {
return 'foo';
}
export function foobar() {
return `${foo()}bar`;
}
Nie jest możliwe mockowanie metody foo
z zewnątrz, ponieważ jest ona bezpośrednio odwoływana. 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):
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żesz potwierdzić to zachowanie, dostarczając implementację metody foobar
bezpośrednio:
import * as mod from './foobar.js';
vi.spyOn(mod, 'foo');
// wyeksportowane foo odwołuje się do zamockowanej metody
mod.foobar(mod.foo);
export function foo() {
return 'foo';
}
export function foobar(injectedFoo) {
return injectedFoo === foo; // false
}
Jest to zamierzone zachowanie. Zazwyczaj jest to oznaka złego kodu, gdy mockowanie jest stosowane w ten sposób. Rozważ refaktoryzację kodu na wiele plików lub ulepszenie architektury aplikacji za pomocą technik takich jak wstrzykiwanie zależności.
Przykład
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Client } from 'pg';
import { failure, success } from './handlers.js';
// pobierz zadania
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 });
});
});
System plików
Mockowanie systemu plików gwarantuje niezależność testów od rzeczywistego systemu plików, co czyni testy bardziej niezawodnymi i przewidywalnymi. Taka izolacja zapobiega efektom ubocznym między testami. Pozwala testować sytuacje błędów i przypadki brzegowe, które mogą być trudne lub niemożliwe do odtworzenia w rzeczywistym systemie plików, takich jak problemy z uprawnieniami, scenariusze pełnego dysku lub błędy odczytu/zapisu.
Vitest domyślnie nie udostępnia żadnego API do mockowania systemu plików. Możesz mockować moduł fs
ręcznie przez vi.mock
, ale utrzymanie takiego kodu jest trudne. Zamiast tego zalecamy użycie memfs
, który automatyzuje ten proces. memfs
tworzy system plików w pamięci, który symuluje operacje na plikach bez dotykania rzeczywistego dysku. To rozwiązanie jest szybkie i bezpieczne, eliminując ryzyko efektów ubocznych w rzeczywistym systemie plików.
Przykład
Aby automatycznie przekierować wszystkie wywołania fs
do memfs
, możesz utworzyć pliki __mocks__/fs.cjs
i __mocks__/fs/promises.cjs
w katalogu głównym swojego projektu:
// możemy również użyć `import`, ale wtedy
// każdy eksport powinien być jawnie zdefiniowany
const { fs } = require('memfs');
module.exports = fs;
// możemy również użyć `import`, ale wtedy
// każdy eksport powinien być jawnie zdefiniowany
const { fs } = require('memfs');
module.exports = fs.promises;
import { readFileSync } from 'node:fs';
export function readHelloWorld(path) {
return readFileSync(path, 'utf-8');
}
import { beforeEach, expect, it, vi } from 'vitest';
import { fs, vol } from 'memfs';
import { readHelloWorld } from './read-hello-world.js';
// powiedz Vitest, aby użył mocka fs z folderu __mocks__
// można to zrobić w pliku setup, jeśli fs zawsze ma być mockowany
vi.mock('node:fs');
vi.mock('node:fs/promises');
beforeEach(() => {
// zresetuj stan systemu plików w pamięci
vol.reset();
});
it('should return correct text', () => {
const path = '/hello-world.txt';
fs.writeFileSync(path, 'hello world');
const text = readHelloWorld(path);
expect(text).toBe('hello world');
});
it('can return a value multiple times', () => {
// możesz użyć vol.fromJSON, aby zdefiniować kilka plików
vol.fromJSON(
{
'./dir1/hw.txt': 'hello dir1',
'./dir2/hw.txt': 'hello dir2',
},
// domyślny katalog roboczy
'/tmp'
);
expect(readHelloWorld('/tmp/dir1/hw.txt')).toBe('hello dir1');
expect(readHelloWorld('/tmp/dir2/hw.txt')).toBe('hello dir2');
});
Żądania
Ponieważ Vitest działa w Node.js, mockowanie żądań sieciowych jest złożone; API webowe nie są dostępne, więc potrzebujemy czegoś, co będzie symulować zachowanie sieciowe. Zalecamy użycie Mock Service Worker do tego celu. Pozwala on mockować żądania sieciowe http
, WebSocket
i GraphQL
i jest niezależny od frameworka.
Mock Service Worker (MSW) działa poprzez przechwytywanie żądań generowanych przez testy, co pozwala na jego użycie bez zmiany kodu aplikacji. W przeglądarce wykorzystuje to Service Worker API. W Node.js i dla Vitest używa biblioteki @mswjs/interceptors
. Aby dowiedzieć się więcej o MSW, przeczytaj ich wprowadzenie
Konfiguracja
Możesz go użyć w swoim pliku konfiguracyjnym w następujący sposób:
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { http, HttpResponse } 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 server = setupServer(...restHandlers);
// Uruchom serwer przed wszystkimi testami
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Zamknij serwer po wszystkich testach
afterAll(() => server.close());
// Zresetuj handlery po każdym teście dla zachowania izolacji
afterEach(() => server.resetHandlers());
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { graphql, HttpResponse } from 'msw';
const posts = [
{
userId: 1,
id: 1,
title: 'first post title',
body: 'first post body',
},
// ...
];
const graphqlHandlers = [
graphql.query('ListPosts', () => {
return HttpResponse.json({
data: { posts },
});
}),
];
const server = setupServer(...graphqlHandlers);
// Uruchom serwer przed wszystkimi testami
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Zamknij serwer po wszystkich testach
afterAll(() => server.close());
// Zresetuj handlery po każdym teście dla zachowania izolacji
afterEach(() => server.resetHandlers());
import { afterAll, afterEach, beforeAll } from 'vitest';
import { setupServer } from 'msw/node';
import { ws } from 'msw';
const chat = ws.link('wss://chat.example.com');
const wsHandlers = [
chat.addEventListener('connection', ({ client }) => {
client.addEventListener('message', event => {
console.log('Received message from client:', event.data);
// Odeślij odebraną wiadomość z powrotem do klienta
client.send(`Server received: ${event.data}`);
});
}),
];
const server = setupServer(...wsHandlers);
// Uruchom serwer przed wszystkimi testami
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Zamknij serwer po wszystkich testach
afterAll(() => server.close());
// Zresetuj handlery po każdym teście dla zachowania izolacji
afterEach(() => server.resetHandlers());
Konfiguracja serwera z opcją
onUnhandledRequest: 'error'
zapewnia, że błąd zostanie zgłoszony, gdy pojawi się żądanie, które nie ma odpowiadającego mu handlera żądań.
Więcej
MSW oferuje znacznie więcej. Możesz uzyskać dostęp do ciasteczek i parametrów zapytania, definiować mockowe odpowiedzi błędów i wiele więcej! Aby zobaczyć wszystko, co możesz zrobić z MSW, przeczytaj ich dokumentację.
Timery
Kiedy testujemy kod, który obejmuje timeouty lub interwały, zamiast czekać na ich naturalne zakończenie lub przekroczenie limitu czasu, możemy przyspieszyć nasze testy, używając „fałszywych” timerów, które mockują wywołania setTimeout
i setInterval
.
Szczegółowy opis API znajdziesz w sekcji vi.useFakeTimers
API.
Przykład
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
function executeAfterTwoHours(func) {
setTimeout(func, 1000 * 60 * 60 * 2); // 2 godziny
}
function executeEveryMinute(func) {
setInterval(func, 1000 * 60); // 1 minuta
}
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);
// przesunięcie o 2ms nie uruchomi funkcji
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);
});
});
Klasy
Możesz zamockować całą klasę za pomocą pojedynczego wywołania vi.fn
- ponieważ wszystkie klasy są również funkcjami, działa to domyślnie. Należy pamiętać, że obecnie Vitest nie uwzględnia słowa kluczowego new
, więc new.target
jest zawsze undefined
w ciele funkcji.
class Dog {
name: string;
constructor(name: string) {
this.name = name;
}
static getType(): string {
return 'animal';
}
greet = (): string => {
return `Hi! My name is ${this.name}!`;
};
speak(): string {
return 'bark!';
}
isHungry() {}
feed() {}
}
Możemy odtworzyć tę klasę za pomocą funkcji ES5:
const Dog = vi.fn(function (name) {
this.name = name;
// mockuj metody instancji w konstruktorze, każda instancja będzie miała własnego szpiega
this.greet = vi.fn(() => `Hi! My name is ${this.name}!`);
});
// zauważ, że metody statyczne są mockowane bezpośrednio na funkcji,
// a nie na instancji klasy
Dog.getType = vi.fn(() => 'mocked animal');
// mockuj metody "speak" i "feed" na każdej instancji klasy
// wszystkie instancje `new Dog()` odziedziczą i będą współdzielić te szpiegi
Dog.prototype.speak = vi.fn(() => 'loud bark!');
Dog.prototype.feed = vi.fn();
WARNING
Jeśli z funkcji konstruktora zostanie zwrócona wartość nieprymitywna, ta wartość stanie się wynikiem wyrażenia new
. W tym przypadku [[Prototype]]
może nie być poprawnie powiązany:
const CorrectDogClass = vi.fn(function (name) {
this.name = name;
});
const IncorrectDogClass = vi.fn(name => ({
name,
}));
const Marti = new CorrectDogClass('Marti');
const Newt = new IncorrectDogClass('Newt');
Marti instanceof CorrectDogClass; // ✅ true
Newt instanceof IncorrectDogClass; // ❌ false!
KIEDY UŻYWAĆ?
Ogólnie rzecz biorąc, zazwyczaj odtwarza się klasę w ten sposób w fabryce modułów, jeśli klasa jest ponownie eksportowana z innego modułu:
import { Dog } from './dog.js';
vi.mock(import('./dog.js'), () => {
const Dog = vi.fn();
Dog.prototype.feed = vi.fn();
// ... inne mocki
return { Dog };
});
Ta metoda może być również użyta do przekazania instancji klasy do funkcji, która akceptuje ten sam interfejs:
function feed(dog: Dog) {
// ...
}
import { expect, test, vi } from 'vitest';
import { feed } from '../src/feed.js';
const Dog = vi.fn();
Dog.prototype.feed = vi.fn();
test('can feed dogs', () => {
const dogMax = new Dog('Max');
feed(dogMax);
expect(dogMax.feed).toHaveBeenCalled();
expect(dogMax.isHungry()).toBe(false);
});
Teraz, gdy tworzymy nową instancję klasy Dog
, jej metoda speak
(wraz z feed
i greet
) jest już zamockowana:
const Cooper = new Dog('Cooper');
Cooper.speak(); // głośne szczekanie!
Cooper.greet(); // Cześć! Nazywam się Cooper!
// możesz użyć wbudowanych asercji, aby sprawdzić poprawność wywołania
expect(Cooper.speak).toHaveBeenCalled();
expect(Cooper.greet).toHaveBeenCalled();
const Max = new Dog('Max');
// metody przypisane do prototypu są współdzielone między instancjami
expect(Max.speak).toHaveBeenCalled();
expect(Max.greet).not.toHaveBeenCalled();
Możemy ponownie przypisać wartość zwracaną dla konkretnej instancji:
const dog = new Dog('Cooper');
// "vi.mocked" to pomocnik typów, ponieważ
// TypeScript nie wie, że Dog to zamockowana klasa,
// opakowuje każdą funkcję w typ MockInstance<T>
// bez walidacji, czy funkcja jest mockiem
vi.mocked(dog.speak).mockReturnValue('hau hau');
dog.speak(); // hau hau
Aby zamockować właściwość, możemy użyć metody vi.spyOn(dog, 'name', 'get')
. Umożliwia to użycie asercji szpiega na zamockowanej właściwości:
const dog = new Dog('Cooper');
const nameSpy = vi.spyOn(dog, 'name', 'get').mockReturnValue('Max');
expect(dog.name).toBe('Max');
expect(nameSpy).toHaveBeenCalledTimes(1);
TIP
Tą samą metodą można też szpiegować gettery i settery.
Ściągawka
INFO
vi
w poniższych przykładach jest importowane bezpośrednio z vitest
. Możesz również używać go globalnie, jeśli ustawisz globals
na true
w swojej konfiguracji.
Chcę…
Mockować wyeksportowane zmienne
export const getter = 'variable';
import * as exports from './example.js';
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked');
Mockować wyeksportowaną funkcję
- Przykład z
vi.mock
:
WARNING
Pamiętaj, że wywołanie vi.mock
jest hoistowane na początek pliku. Zawsze zostanie wykonane przed wszystkimi importami.
export function method() {}
import { method } from './example.js';
vi.mock('./example.js', () => ({
method: vi.fn(),
}));
- Przykład z
vi.spyOn
:
import * as exports from './example.js';
vi.spyOn(exports, 'method').mockImplementation(() => {});
Mockować implementację wyeksportowanej klasy
- Przykład z
vi.mock
i.prototype
:
export class SomeClass {}
import { SomeClass } from './example.js';
vi.mock(import('./example.js'), () => {
const SomeClass = vi.fn();
SomeClass.prototype.someMethod = vi.fn();
return { SomeClass };
});
// SomeClass.mock.instances będzie instancją SomeClass
- Przykład z
vi.spyOn
:
import * as mod from './example.js';
const SomeClass = vi.fn();
SomeClass.prototype.someMethod = vi.fn();
vi.spyOn(mod, 'SomeClass').mockImplementation(SomeClass);
Szpiegować obiekt zwrócony z funkcji
- Przykład użycia pamięci podręcznej:
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(),
};
}
// teraz za każdym razem, gdy wywoływane jest useObject(),
// zwróci ono to samo odwołanie do obiektu
return _cache;
};
return { useObject };
});
const obj = useObject();
// obj.method zostało wywołane wewnątrz some-path
expect(obj.method).toHaveBeenCalled();
Mockować część modułu
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(); // ma oryginalne zachowanie
mocked(); // jest funkcją szpiegującą
WARNING
Nie zapominaj, że to tylko mockuje dostęp zewnętrzny. W tym przykładzie, jeśli original
wywołuje mocked
wewnętrznie, zawsze wywoła funkcję zdefiniowaną w module, a nie w fabryce mocków.
Mockować bieżącą datę
Aby zamockować czas Date
, możesz użyć funkcji pomocniczej vi.setSystemTime
. Ta wartość nie zostanie automatycznie zresetowana między różnymi testami.
Należy pamiętać, że użycie vi.useFakeTimers
również zmienia czas Date
.
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// zresetuj zamockowany czas
vi.useRealTimers();
Mockować zmienną globalną
Możesz ustawić zmienną globalną, przypisując jej wartość do globalThis
lub używając pomocnika vi.stubGlobal
. Podczas używania vi.stubGlobal
, nie zostanie ona automatycznie zresetowana między różnymi testami, chyba że włączysz opcję konfiguracyjną unstubGlobals
lub wywołasz vi.unstubAllGlobals
.
vi.stubGlobal('__VERSION__', '1.0.0');
expect(__VERSION__).toBe('1.0.0');
Mockować import.meta.env
- 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.
import { beforeEach, expect, it } from 'vitest';
// przed uruchomieniem testów "VITE_ENV" jest "test"
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');
});
- Jeśli chcesz automatycznie zresetować wartość(i), możesz użyć pomocnika
vi.stubEnv
z włączoną opcją konfiguracyjnąunstubEnvs
(lub ręcznie wywołaćvi.unstubAllEnvs
w hookubeforeEach
):
import { expect, it, vi } from 'vitest';
// przed uruchomieniem testów "VITE_ENV" jest "test"
import.meta.env.VITE_ENV === 'test';
it('changes value', () => {
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');
});
export default defineConfig({
test: {
unstubEnvs: true,
},
});