Mocking
Beim Schreiben von Tests ist es oft notwendig, eine "fiktive" oder "simulierte" Version eines internen oder externen Dienstes zu erstellen. Dies wird allgemein als Mocking bezeichnet. Vitest bietet hierfür nützliche Funktionen über seinen vi
-Helfer. Sie können diesen entweder aus vitest
importieren oder global darauf zugreifen, falls die global
-Konfiguration aktiviert ist.
WARNING
Denken Sie immer daran, Mocks vor oder nach jedem Testlauf zurückzusetzen oder wiederherzustellen, um den Zustand der Mocks zwischen den Testläufen zu isolieren! Weitere Informationen finden Sie in der Dokumentation zu mockReset
.
Falls Sie mit den Methoden vi.fn
, vi.mock
oder vi.spyOn
noch nicht vertraut sind, empfiehlt es sich, zuerst den API-Abschnitt zu lesen.
Datumsangaben
Manchmal ist es erforderlich, die Kontrolle über das Datum zu übernehmen, um Konsistenz beim Testen zu gewährleisten. Vitest verwendet das Paket @sinonjs/fake-timers
zur Manipulation von Timern und des Systemdatums. Detaillierte Informationen zur spezifischen API finden Sie hier.
Beispiel
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 informieren, dass wir gefälschte Zeit verwenden
vi.useFakeTimers();
});
afterEach(() => {
// Datum nach jedem Testlauf wiederherstellen
vi.useRealTimers();
});
it('allows purchases within business hours', () => {
// Stunde innerhalb der Geschäftszeiten einstellen
const date = new Date(2000, 1, 1, 13);
vi.setSystemTime(date);
// Der Zugriff auf Date.now() gibt das oben eingestellte Datum zurück
expect(purchase()).toEqual({ message: 'Success' });
});
it('disallows purchases outside of business hours', () => {
// Stunde außerhalb der Geschäftszeiten einstellen
const date = new Date(2000, 1, 1, 19);
vi.setSystemTime(date);
// Der Zugriff auf Date.now() gibt das oben eingestellte Datum zurück
expect(purchase()).toEqual({ message: 'Error' });
});
});
Funktionen
Das Mocken von Funktionen lässt sich in zwei Kategorien unterteilen: Ausspionieren und Mocken.
Manchmal müssen Sie lediglich überprüfen, ob eine bestimmte Funktion aufgerufen wurde (und welche Argumente dabei übergeben wurden). In solchen Fällen genügt ein Spy, den Sie direkt mit vi.spyOn()
verwenden können (lesen Sie hier mehr).
Spies können Funktionen jedoch nur ausspionieren, ihre Implementierung aber nicht ändern. Wenn wir eine gefälschte (oder gemockte) Version einer Funktion erstellen müssen, können wir vi.fn()
verwenden (lesen Sie hier mehr).
Wir verwenden Tinyspy als Basis für das Mocken von Funktionen, haben aber eine eigene Hülle, um es jest
-kompatibel zu machen. Sowohl vi.fn()
als auch vi.spyOn()
verfügen über dieselben Methoden; allerdings ist nur das Rückgabeergebnis von vi.fn()
aufrufbar.
Beispiel
import { afterEach, describe, expect, it, vi } from 'vitest';
const messages = {
items: [
{ message: 'Simple test message', from: 'Testman' },
// ...
],
getLatest, // kann auch ein `getter oder setter sein, falls unterstützt`
};
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);
});
});
Mehr
Globale Variablen
Sie können globale Variablen, die nicht standardmäßig in jsdom
oder node
vorhanden sind, mit dem Helfer vi.stubGlobal
mocken. Dieser Helfer weist den Wert der globalen Variable einem globalThis
-Objekt zu.
import { vi } from 'vitest';
const IntersectionObserverMock = vi.fn(() => ({
disconnect: vi.fn(),
observe: vi.fn(),
takeRecords: vi.fn(),
unobserve: vi.fn(),
}));
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
// jetzt können Sie darauf als `IntersectionObserver` oder `window.IntersectionObserver` zugreifen
Module
Mock-Module ermöglichen es, Drittanbieter-Bibliotheken, die in anderem Code aufgerufen werden, zu überwachen, sodass Sie Argumente und Ausgaben testen oder sogar deren Implementierung neu deklarieren können.
Weitere Details zur API finden Sie im Abschnitt vi.mock()
API.
Automocking-Algorithmus
Wenn Ihr Code ein gemocktes Modul importiert, ohne eine zugehörige __mocks__
-Datei oder factory
für dieses Modul, wird Vitest das Modul selbst mocken, indem es es aufruft und alle Exporte mockt.
Es gelten die folgenden Prinzipien:
- Alle Arrays werden geleert.
- Alle primitiven Werte und Sammlungen bleiben gleich.
- Alle Objekte werden tief geklont.
- Alle Instanzen von Klassen und ihre Prototypen werden tief geklont.
Virtuelle Module
Vitest unterstützt das Mocken von Vite virtuellen Modulen. Dies funktioniert anders als die Behandlung virtueller Module in Jest. Anstatt virtual: true
an eine vi.mock
-Funktion zu übergeben, müssen Sie Vite mitteilen, dass das Modul existiert, da es sonst beim Parsen fehlschlagen würde. Dies können Sie auf verschiedene Arten tun:
- Einen Alias bereitstellen:
import { defineConfig } from 'vitest/config';
import { resolve } from 'node:path';
export default defineConfig({
test: {
alias: {
'$app/forms': resolve('./mocks/forms.js'),
},
},
});
- Ein Plugin bereitstellen, das ein virtuelles Modul auflöst:
import { defineConfig } from 'vitest/config';
export default defineConfig({
plugins: [
{
name: 'virtual-modules',
resolveId(id) {
if (id === '$app/forms') {
return 'virtual:$app/forms';
}
},
},
],
});
Der zweite Ansatz hat den Vorteil, dass Sie verschiedene virtuelle Einstiegspunkte dynamisch erstellen können. Wenn Sie mehrere virtuelle Module in eine einzige Datei umleiten, werden alle von vi.mock
betroffen sein. Stellen Sie daher sicher, dass Sie eindeutige Bezeichner verwenden.
Mocking-Fallstricke
Beachten Sie, dass Aufrufe von Methoden, die innerhalb anderer Methoden derselben Datei aufgerufen werden, nicht gemockt werden können. Zum Beispiel in diesem Code:
export function foo() {
return 'foo';
}
export function foobar() {
return `${foo()}bar`;
}
Es ist nicht möglich, die foo
-Methode von außen zu mocken, da sie direkt referenziert wird. Dieser Code hat also keine Auswirkung auf den foo
-Aufruf innerhalb von foobar
(aber er wirkt sich auf den foo
-Aufruf in anderen Modulen aus):
import { vi } from 'vitest';
import * as mod from './foobar.js';
// dies betrifft nur "foo" außerhalb des ursprünglichen Moduls
vi.spyOn(mod, 'foo');
vi.mock('./foobar.js', async importOriginal => {
return {
...(await importOriginal<typeof import('./foobar.js')>()),
// dies betrifft nur "foo" außerhalb des ursprünglichen Moduls
foo: () => 'mocked',
};
});
Sie können dieses Verhalten bestätigen, indem Sie die Implementierung direkt der foobar
-Methode bereitstellen:
import * as mod from './foobar.js';
vi.spyOn(mod, 'foo');
// exportiertes foo referenziert gemockte Methode
mod.foobar(mod.foo);
export function foo() {
return 'foo';
}
export function foobar(injectedFoo) {
return injectedFoo === foo; // false
}
Dies ist das beabsichtigte Verhalten. Dieses Verhalten deutet meist auf schlechten Code hin, wenn Mocking auf diese Weise eingesetzt wird. Erwägen Sie, Ihren Code in mehrere Dateien zu refaktorieren oder Ihre Anwendungsarchitektur durch Techniken wie Abhängigkeitsinjektion zu verbessern.
Beispiel
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { Client } from 'pg';
import { failure, success } from './handlers.js';
// Todos abrufen
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 });
});
});
Dateisystem
Das Mocken des Dateisystems stellt sicher, dass Tests nicht vom tatsächlichen Dateisystem abhängen, und macht sie dadurch zuverlässiger und vorhersehbarer. Diese Isolation hilft dabei, Nebenwirkungen aus früheren Tests zu vermeiden. Sie ermöglicht das Testen von Fehlerbedingungen und Grenzfällen, die mit einem tatsächlichen Dateisystem schwierig oder unmöglich zu reproduzieren wären, wie z. B. Berechtigungsprobleme, volle Festplatten oder Lese-/Schreibfehler.
Vitest bietet keine Dateisystem-Mocking-API von Haus aus. Sie könnten vi.mock
verwenden, um das fs
-Modul manuell zu mocken, dies ist jedoch schwer zu pflegen. Stattdessen empfehlen wir die Verwendung von memfs
, das ein In-Memory-Dateisystem erstellt, das Dateisystemoperationen simuliert, ohne die tatsächliche Festplatte zu berühren. Dieser Ansatz ist schnell und sicher, wodurch potenzielle Nebenwirkungen auf das reale Dateisystem vermieden werden.
Beispiel
Um jeden fs
-Aufruf automatisch an memfs
umzuleiten, können Sie die Dateien __mocks__/fs.cjs
und __mocks__/fs/promises.cjs
im Stammverzeichnis Ihres Projekts erstellen:
// wir können auch `import` verwenden, aber dann
// sollte jeder Export explizit definiert werden
const { fs } = require('memfs');
module.exports = fs;
// wir können auch `import` verwenden, aber dann
// sollte jeder Export explizit definiert werden
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';
// Vitest anweisen, fs-Mock aus dem __mocks__-Ordner zu verwenden
// Dies kann in einer Setup-Datei erfolgen, wenn fs immer gemockt werden soll
vi.mock('node:fs');
vi.mock('node:fs/promises');
beforeEach(() => {
// den Zustand des In-Memory-Dateisystems zurücksetzen
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', () => {
// Sie können vol.fromJSON verwenden, um mehrere Dateien zu definieren
vol.fromJSON(
{
'./dir1/hw.txt': 'hello dir1',
'./dir2/hw.txt': 'hello dir2',
},
// Standard-CWD
'/tmp'
);
expect(readHelloWorld('/tmp/dir1/hw.txt')).toBe('hello dir1');
expect(readHelloWorld('/tmp/dir2/hw.txt')).toBe('hello dir2');
});
Anfragen
Da Vitest in Node.js läuft, ist das Mocken von Netzwerkanfragen komplex; Web-APIs sind nicht direkt verfügbar, daher benötigen wir eine Lösung, die das Netzwerkverhalten nachahmt. Wir empfehlen Mock Service Worker, um dies zu erreichen. MSW ermöglicht es Ihnen, http
-, WebSocket
- und GraphQL
-Netzwerkanfragen zu mocken und ist frameworkunabhängig.
Mock Service Worker (MSW) funktioniert, indem es die Anfragen Ihrer Tests abfängt, sodass Sie es verwenden können, ohne Ihren Anwendungscode zu ändern. Im Browser wird hierfür die Service Worker API verwendet. In Node.js und für Vitest wird die Bibliothek @mswjs/interceptors
eingesetzt. Um mehr über MSW zu erfahren, lesen Sie die Einführung dazu.
Konfiguration
Sie können es wie unten in Ihrer Setup-Datei verwenden:
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);
// Server vor allen Tests starten
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Server nach allen Tests schließen
afterAll(() => server.close());
// Handler nach jedem Test für Testisolation zurücksetzen
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);
// Server vor allen Tests starten
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Server nach allen Tests schließen
afterAll(() => server.close());
// Handler nach jedem Test für Testisolation zurücksetzen
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('Nachricht vom Client empfangen:', event.data);
// Empfangene Nachricht an den Client zurücksenden
client.send(`Server empfangen: ${event.data}`);
});
}),
];
const server = setupServer(...wsHandlers);
// Server vor allen Tests starten
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
// Server nach allen Tests schließen
afterAll(() => server.close());
// Handler nach jedem Test für Testisolation zurücksetzen
afterEach(() => server.resetHandlers());
Die Konfiguration des Servers mit
onUnhandledRequest: 'error'
stellt sicher, dass ein Fehler ausgelöst wird, sobald eine Anfrage keinen entsprechenden Anfrage-Handler besitzt.
Mehr
Es gibt noch viel mehr zu MSW. Sie können auf Cookies und Abfrageparameter zugreifen, Mock-Fehlerantworten definieren und vieles mehr! Um alles zu sehen, was Sie mit MSW tun können, lesen Sie deren Dokumentation.
Timer
Wenn wir Code testen, der Timeouts oder Intervalle beinhaltet, können wir unsere Tests beschleunigen, indem wir "gefälschte" Timer verwenden, die Aufrufe an setTimeout
und setInterval
mocken. So müssen unsere Tests nicht abwarten oder in einen Timeout laufen.
Weitere Details zur API finden Sie im Abschnitt vi.useFakeTimers
API.
Beispiel
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
function executeAfterTwoHours(func) {
setTimeout(func, 1000 * 60 * 60 * 2); // 2 Stunden
}
function executeEveryMinute(func) {
setInterval(func, 1000 * 60); // 1 Minute
}
const mock = vi.fn(() => console.log('ausgeführt'));
describe('verzögerte Ausführung', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.restoreAllMocks();
});
it('sollte die Funktion ausführen', () => {
executeAfterTwoHours(mock);
vi.runAllTimers();
expect(mock).toHaveBeenCalledTimes(1);
});
it('sollte die Funktion nicht ausführen', () => {
executeAfterTwoHours(mock);
// um 2ms vorrücken löst die Funktion nicht aus
vi.advanceTimersByTime(2);
expect(mock).not.toHaveBeenCalled();
});
it('sollte jede Minute ausgeführt werden', () => {
executeEveryMinute(mock);
vi.advanceTimersToNextTimer();
expect(mock).toHaveBeenCalledTimes(1);
vi.advanceTimersToNextTimer();
expect(mock).toHaveBeenCalledTimes(2);
});
});
Klassen
Sie können eine ganze Klasse mit einem einzigen vi.fn
-Aufruf mocken – da alle Klassen auch Funktionen sind, funktioniert dies von Haus aus. Beachten Sie, dass Vitest derzeit das new
-Schlüsselwort nicht berücksichtigt, weshalb new.target
im Funktionskörper immer undefined
ist.
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() {}
}
Wir können diese Klasse mit ES5-Funktionen neu erstellen:
const Dog = vi.fn(function (name) {
this.name = name;
// Instanzmethoden im Konstruktor mocken, jede Instanz hat ihren eigenen Spy
this.greet = vi.fn(() => `Hi! My name is ${this.name}!`);
});
// Beachten Sie, dass statische Methoden direkt an der Funktion gemockt werden,
// nicht an der Instanz der Klasse
Dog.getType = vi.fn(() => 'mocked animal');
// die Methoden "speak" und "feed" an jeder Instanz einer Klasse mocken
// alle `new Dog()`-Instanzen erben und teilen diese Spies
Dog.prototype.speak = vi.fn(() => 'loud bark!');
Dog.prototype.feed = vi.fn();
WARNING
Wenn ein nicht-primitiver Wert von der Konstruktorfunktion zurückgegeben wird, wird dieser Wert das Ergebnis des neuen Ausdrucks. In diesem Fall ist das [[Prototype]]
möglicherweise nicht korrekt verknüpft:
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!
WANN ZU VERWENDEN?
Im Allgemeinen erstellt man eine Klasse wie diese innerhalb der Modulfactory neu, wenn die Klasse aus einem anderen Modul re-exportiert wird:
import { Dog } from './dog.js';
vi.mock(import('./dog.js'), () => {
const Dog = vi.fn();
Dog.prototype.feed = vi.fn();
// ... andere Mocks
return { Dog };
});
Diese Methode kann auch verwendet werden, um eine Instanz einer Klasse an eine Funktion zu übergeben, die eine kompatible Schnittstelle erwartet:
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);
});
Wenn wir nun eine neue Instanz der Dog
-Klasse erstellen, sind ihre speak
-Methode (neben feed
und greet
) bereits gemockt:
const Cooper = new Dog('Cooper');
Cooper.speak(); // bellt laut!
Cooper.greet(); // Hallo! Mein Name ist Cooper!
// Sie können die integrierten Assertions verwenden, um die Gültigkeit des Aufrufs zu überprüfen
expect(Cooper.speak).toHaveBeenCalled();
expect(Cooper.greet).toHaveBeenCalled();
const Max = new Dog('Max');
// Methoden, die dem Prototyp zugewiesen wurden, werden zwischen Instanzen geteilt
expect(Max.speak).toHaveBeenCalled();
expect(Max.greet).not.toHaveBeenCalled();
Wir können den Rückgabewert für eine bestimmte Instanz neu zuweisen:
const dog = new Dog('Cooper');
// "vi.mocked" ist ein Typ-Helfer, da
// TypeScript nicht weiß, dass Dog eine gemockte Klasse ist,
// es wickelt jede Funktion in einen MockInstance<T>-Typ ein,
// ohne zu validieren, ob die Funktion ein Mock ist
vi.mocked(dog.speak).mockReturnValue('wuff wuff');
dog.speak(); // wuff wuff
Um die Eigenschaft zu mocken, können wir die Methode vi.spyOn(dog, 'name', 'get')
verwenden. Dies ermöglicht es, Spy-Assertions auf der gemockten Eigenschaft zu verwenden:
const dog = new Dog('Cooper');
const nameSpy = vi.spyOn(dog, 'name', 'get').mockReturnValue('Max');
expect(dog.name).toBe('Max');
expect(nameSpy).toHaveBeenCalledTimes(1);
TIP
Sie können auch Getter und Setter mit derselben Methode ausspionieren.
Spickzettel
INFO
vi
in den folgenden Beispielen wird direkt aus vitest
importiert. Sie können es auch global verwenden, wenn Sie globals
in Ihrer Konfiguration auf true
setzen.
Ich möchte…
Exportierte Variablen mocken
export const getter = 'variable';
import * as exports from './example.js';
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked');
Eine exportierte Funktion mocken
- Beispiel mit
vi.mock
:
WARNING
Vergessen Sie nicht, dass ein vi.mock
-Aufruf an den Anfang der Datei verschoben wird. Er wird immer vor allen Importen ausgeführt.
export function method() {}
import { method } from './example.js';
vi.mock('./example.js', () => ({
method: vi.fn(),
}));
- Beispiel mit
vi.spyOn
:
import * as exports from './example.js';
vi.spyOn(exports, 'method').mockImplementation(() => {});
Eine exportierte Klassenimplementierung mocken
- Beispiel mit
vi.mock
und.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 wird Instanzen von SomeClass enthalten.
- Beispiel mit
vi.spyOn
:
import * as mod from './example.js';
const SomeClass = vi.fn();
SomeClass.prototype.someMethod = vi.fn();
vi.spyOn(mod, 'SomeClass').mockImplementation(SomeClass);
Ein von einer Funktion zurückgegebenes Objekt ausspionieren
- Beispiel unter Verwendung eines Caches:
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(),
};
}
// jetzt wird jedes Mal, wenn useObject() aufgerufen wird,
// dieselbe Objektreferenz zurückgegeben
return _cache;
};
return { useObject };
});
const obj = useObject();
// obj.method wurde innerhalb von 'some-path' aufgerufen
expect(obj.method).toHaveBeenCalled();
Teil eines Moduls mocken
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(); // hat ursprüngliches Verhalten
mocked(); // ist eine Spy-Funktion
WARNING
Vergessen Sie nicht, dass dies nur den externen Zugriff mockt. In diesem Beispiel wird, wenn original
intern mocked
aufruft, immer die im Modul definierte Funktion aufgerufen, nicht die in der Mock-Factory.
Das aktuelle Datum mocken
Um die Zeit von Date
zu mocken, können Sie die Hilfsfunktion vi.setSystemTime
verwenden. Dieser Wert wird nicht automatisch zwischen verschiedenen Tests zurückgesetzt.
Beachten Sie, dass die Verwendung von vi.useFakeTimers
auch die Zeit von Date
ändert.
const mockDate = new Date(2022, 0, 1);
vi.setSystemTime(mockDate);
const now = new Date();
expect(now.valueOf()).toBe(mockDate.valueOf());
// gemockte Zeit zurücksetzen
vi.useRealTimers();
Eine globale Variable mocken
Sie können eine globale Variable setzen, indem Sie globalThis
einen Wert zuweisen oder den Helfer vi.stubGlobal
verwenden. Bei Verwendung von vi.stubGlobal
wird der Wert nicht automatisch zwischen verschiedenen Tests zurückgesetzt, es sei denn, Sie aktivieren die Konfigurationsoption unstubGlobals
oder rufen vi.unstubAllGlobals
manuell auf.
vi.stubGlobal('__VERSION__', '1.0.0');
expect(__VERSION__).toBe('1.0.0');
import.meta.env
mocken
- Um eine Umgebungsvariable zu ändern, können Sie ihr einfach einen neuen Wert zuweisen.
WARNING
Der Wert der Umgebungsvariablen wird nicht automatisch zwischen verschiedenen Tests zurückgesetzt.
import { beforeEach, expect, it } from 'vitest';
// Vor dem Ausführen der Tests ist "VITE_ENV" "test"
const originalViteEnv = import.meta.env.VITE_ENV;
beforeEach(() => {
import.meta.env.VITE_ENV = originalViteEnv;
});
it('ändert den Wert', () => {
import.meta.env.VITE_ENV = 'staging';
expect(import.meta.env.VITE_ENV).toBe('staging');
});
- Wenn Sie die Werte automatisch zurücksetzen möchten, können Sie den Helfer
vi.stubEnv
mit aktivierter KonfigurationsoptionunstubEnvs
verwenden (odervi.unstubAllEnvs
manuell in einembeforeEach
-Hook aufrufen):
import { expect, it, vi } from 'vitest';
// vor dem Ausführen der Tests ist "VITE_ENV" "test"
import.meta.env.VITE_ENV === 'test';
it('ändert den Wert', () => {
vi.stubEnv('VITE_ENV', 'staging');
expect(import.meta.env.VITE_ENV).toBe('staging');
});
it('Der Wert wird vor dem Ausführen eines weiteren Tests wiederhergestellt', () => {
expect(import.meta.env.VITE_ENV).toBe('test');
});
export default defineConfig({
test: {
unstubEnvs: true,
},
});