Mock-funksjoner
This page was AI-translated by PageTurner (beta). Not officially endorsed by the project. Found an error? Report issue →
Mock-funksjoner lar deg teste sammenhenger i koden ved å erstatte den faktiske implementasjonen av en funksjon, fange opp kall til funksjonen (med parametere som ble sendt), fange instanser av konstruktørfunksjoner når de instansieres med new, og konfigurere returverdier under testing.
Det er to måter å mocke funksjoner på: Enten ved å opprette en mock-funksjon for bruk i testkode, eller ved å skrive en manual mock for å overstyre en modulavhengighet.
Bruke en mock-funksjon
La oss anta at vi tester en implementasjon av funksjonen forEach, som kaller en tilbakekallingsfunksjon for hvert element i en gitt array.
export function forEach(items, callback) {
for (const item of items) {
callback(item);
}
}
For å teste denne funksjonen kan vi bruke en mock-funksjon og undersøke mockens tilstand for å verifisere at tilbakekallingsfunksjonen blir kalt som forventet.
import {forEach} from './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);
});
.mock-egenskapen
Alle mock-funksjoner har denne spesielle .mock-egenskapen, som lagrer data om hvordan funksjonen ble kalt og hva den returnerte. .mock-egenskapen sporer også this-verdien for hvert kall, så du kan også inspisere denne:
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> ]
Disse mock-egenskapene er svært nyttige i tester for å verifisere hvordan funksjoner blir kalt, instansiert eller hva de returnerte:
// 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-returverdier
Mock-funksjoner kan også brukes til å injisere testverdier i koden din under en test:
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-funksjoner er også svært effektive i kode som bruker en funksjonell kontinuerlig overføringsstil. Slik kode unngår behovet for kompliserte stubber som etterligner oppførselen til den egentlige komponenten de erstatter, til fordel for å injisere verdier direkte i testen rett før de brukes.
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
De fleste virkelige eksempler involverer å få tak i en mock-funksjon på en avhengig komponent og konfigurere den, men teknikken er den samme. I slike tilfeller bør du unngå fristelsen til å implementere logikk i funksjoner som ikke direkte blir testet.
Mocking av moduler
Anta at vi har en klasse som henter brukere fra vår API. Klassen bruker axios til å kalle API-en og returnerer deretter data-attributtet som inneholder alle brukerne:
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
For å teste denne metoden uten faktisk å treffe API-en (og dermed skape trege og skjøre tester), kan vi bruke jest.mock(...)-funksjonen til å automagisk mocke axios-modulen.
Når vi har mocket modulen kan vi angi en mockResolvedValue for .get som returnerer dataene vi vil at testen skal verifisere. I praksis sier vi at vi ønsker at axios.get('/users.json') skal returnere et falskt svar.
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));
});
Delvis mocking
Deler av en modul kan mockes mens resten av modulen beholder sin faktiske implementasjon:
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-implementasjoner
Likevel er det tilfeller der det er nyttig å gå lenger enn å spesifisere returverdier og erstatte hele implementasjonen av en mock-funksjon. Dette kan gjøres med jest.fn eller mockImplementationOnce-metoden på mock-funksjoner.
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true
mockImplementation-metoden er nyttig når du må definere standardimplementasjonen til en mock-funksjon som opprettes fra en annen modul:
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
Når du trenger å gjenskape kompleks oppførsel i en mock-funksjon der flere funksjonskall gir ulike resultater, bruk mockImplementationOnce-metoden:
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
Når den mockede funksjonen har brukt opp alle implementasjoner definert med mockImplementationOnce, vil den kjøre standardimplementasjonen satt med jest.fn (hvis definert):
const myMockFn = jest
.fn(() => 'default')
.mockImplementationOnce(() => 'first call')
.mockImplementationOnce(() => 'second call');
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());
// > 'first call', 'second call', 'default', 'default'
For metoder som typisk lenkes (og derfor alltid må returnere this), tilbyr vi en forenklet API i form av .mockReturnThis()-funksjonen som finnes på alle mock-objekter:
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};
// is the same as
const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};
Mock-navn
Du kan valgfritt gi et navn til dine mock-funksjoner, som vil vises i stedet for 'jest.fn()' i testfeilene. Bruk .mockName() hvis du raskt vil kunne identifisere mock-funksjonen som rapporterer en feil i testresultatene dine.
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');
Egendefinerte matchere
Til slutt, for å gjøre det enklere å bekrefte hvordan mock-funksjoner er blitt kalt, har vi lagt til noen egendefinerte matcher-funksjoner for deg:
// 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();
Disse matcherene er en forenkling for vanlige måter å inspisere .mock-egenskapen på. Du kan alltid gjøre dette manuelt selv hvis du foretrekker det eller trenger å gjøre noe mer spesifikt:
// 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 of an 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');
For en komplett liste over matchere, se referansedokumentasjonen.