Mock-Funktionen
Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →
Mock-Funktionen ermöglichen es, Verbindungen zwischen Codeabschnitten zu testen, indem sie die tatsächliche Implementierung einer Funktion ersetzen. Sie erfassen Aufrufe der Funktion (sowie die dabei übergebenen Parameter), erfassen Instanzen von Konstruktorfunktionen bei Instanziierung mit new und erlauben die Konfiguration von Rückgabewerten während des Tests.
Es gibt zwei Möglichkeiten, Funktionen zu mocken: Entweder durch Erstellen einer Mock-Funktion für Testcode oder durch Schreiben eines manual mock, um eine Modulabhängigkeit zu überschreiben.
Verwendung einer Mock-Funktion
Stellen wir uns vor, wir testen eine Implementierung der Funktion forEach, die für jedes Element in einem bereitgestellten Array einen Callback aufruft.
export function forEach(items, callback) {
for (const item of items) {
callback(item);
}
}
Um diese Funktion zu testen, können wir eine Mock-Funktion verwenden und den Zustand des Mocks überprüfen, um sicherzustellen, dass der Callback wie erwartet aufgerufen wird.
const forEach = require('./forEach');
const mockCallback = jest.fn(x => 42 + x);
test('forEach mock function', () => {
forEach([0, 1], mockCallback);
// The mock function was called twice
expect(mockCallback.mock.calls).toHaveLength(2);
// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);
});
Die .mock-Eigenschaft
Alle Mock-Funktionen besitzen diese spezielle .mock-Eigenschaft, in der Daten darüber gespeichert werden, wie die Funktion aufgerufen wurde und was sie zurückgegeben hat. Die .mock-Eigenschaft verfolgt auch den Wert von this für jeden Aufruf, sodass auch dieser überprüft werden kann:
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]
Diese Mock-Member sind in Tests sehr nützlich, um zu überprüfen, wie Funktionen aufgerufen, instanziiert oder was zurückgegeben wurde:
// The function was called exactly once
expect(someMockFunction.mock.calls).toHaveLength(1);
// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');
// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);
// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);
// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toBe('test');
// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');
Mock-Rückgabewerte
Mock-Funktionen können auch verwendet werden, um Testwerte während eines Tests in Ihren Code einzuspeisen:
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
Mock-Funktionen sind auch sehr effektiv in Code, der einen funktionalen Continuation-Passing-Stil verwendet. Code in diesem Stil vermeidet den Bedarf an komplizierten Stubs, die das Verhalten der echten Komponente nachbilden, und setzt stattdessen direkt vor der Verwendung Testwerte ein.
const filterTestFn = jest.fn();
// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12
Die meisten Praxisbeispiele beinhalten tatsächlich das Zugreifen auf eine Mock-Funktion einer abhängigen Komponente und deren Konfiguration, aber die Technik ist dieselbe. Versuchen Sie in solchen Fällen, der Versuchung zu widerstehen, Logik in Funktionen zu implementieren, die nicht direkt getestet werden.
Mocking von Modulen
Angenommen, wir haben eine Klasse, die Benutzer von unserer API abruft. Die Klasse verwendet axios, um die API aufzurufen, und gibt dann das data-Attribut zurück, das alle Benutzer enthält:
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
Um diese Methode zu testen, ohne tatsächlich die API aufzurufen (und damit langsame und fehleranfällige Tests zu erzeugen), können wir die Funktion jest.mock(...) verwenden, um das Axios-Modul automatisch zu mocken.
Sobald wir das Modul gemockt haben, können wir für .get einen mockResolvedValue bereitstellen, der die Daten zurückgibt, gegen die unser Test prüfen soll. Effektiv sagen wir damit, dass axios.get('/users.json') eine simulierte Antwort zurückgeben soll.
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Partielles Mocking
Teilmengen eines Moduls können gemockt werden, während der Rest des Moduls seine tatsächliche Implementierung beibehält:
export const foo = 'foo';
export const bar = () => 'bar';
export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';
jest.mock('../foo-bar-baz', () => {
const originalModule = jest.requireActual('../foo-bar-baz');
//Mock the default export and named export 'foo'
return {
__esModule: true,
...originalModule,
default: jest.fn(() => 'mocked baz'),
foo: 'mocked foo',
};
});
test('should do a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked baz');
expect(defaultExport).toHaveBeenCalled();
expect(foo).toBe('mocked foo');
expect(bar()).toBe('bar');
});
Mock-Implementierungen
Dennoch gibt es Fälle, in denen es nützlich ist, über die Festlegung von Rückgabewerten hinauszugehen und die gesamte Implementierung einer Mock-Funktion zu ersetzen. Dies kann mit jest.fn oder der Methode mockImplementationOnce auf Mock-Funktionen erfolgen.
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true
Die mockImplementation-Methode ist nützlich, wenn Sie die Standardimplementierung einer Mock-Funktion definieren müssen, die aus einem anderen Modul stammt:
module.exports = function () {
// some implementation;
};
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');
// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42
Wenn Sie ein komplexes Verhalten einer Mock-Funktion nachbilden müssen, bei dem mehrere Funktionsaufrufe unterschiedliche Ergebnisse liefern, verwenden Sie die mockImplementationOnce-Methode:
const myMockFn = jest
.fn()
.mockImplementationOnce(cb => cb(null, true))
.mockImplementationOnce(cb => cb(null, false));
myMockFn((err, val) => console.log(val));
// > true
myMockFn((err, val) => console.log(val));
// > false
Wenn der gemockten Funktion die mit mockImplementationOnce definierten Implementierungen ausgehen, führt sie die Standardimplementierung aus, die mit jest.fn festgelegt wurde (sofern definiert):
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'
Für Fälle, in denen wir Methoden haben, die typischerweise verkettet werden (und daher immer this zurückgeben müssen), gibt es eine vereinfachende API in Form einer .mockReturnThis()-Funktion, die ebenfalls bei allen Mocks verfügbar ist:
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};
// is the same as
const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};
Mock-Namen
Optional können Sie Ihren Mock-Funktionen einen Namen geben, der in der Testfehlerausgabe anstelle von 'jest.fn()' angezeigt wird. Verwenden Sie .mockName(), wenn Sie die Mock-Funktion, die einen Fehler meldet, schnell identifizieren möchten.
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');
Benutzerdefinierte Matcher
Um die Überprüfung von Mock-Funktionsaufrufen zu vereinfachen, haben wir einige spezielle Matcher-Funktionen hinzugefügt:
// The mock function was called at least once
expect(mockFunc).toHaveBeenCalled();
// The mock function was called at least once with the specified args
expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);
// The last call to the mock function was called with the specified args
expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);
// All calls and the name of the mock is written as a snapshot
expect(mockFunc).toMatchSnapshot();
Diese Matcher sind praktische Kurzformen für gängige Prüfungen der .mock-Eigenschaft. Natürlich können Sie diese Prüfungen auch manuell durchführen, wenn Sie dies bevorzugen oder spezifischere Anforderungen haben:
// The mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);
// The mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);
// The last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
arg1,
arg2,
]);
// The first arg of the last call to the mock function was `42`
// (note that there is no sugar helper for this specific assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);
// A snapshot will check that a mock was invoked the same number of times,
// in the same order, with the same arguments. It will also assert on the name.
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');
Eine vollständige Liste der Matcher finden Sie in der Referenzdokumentation.