Mocking af native moduler og asynkron kode
Mock expo-location og andre native moduler i Jest-opsætningsfiler, brug jest.fn() til callbacks, og test asynkrone flows med waitFor- og findBy-forespørgsler.
Mocking af native moduler og asynkron kode er en gratis React Native Academy-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i React Native Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. React Native Academy-kurset indeholder 4 lektioner i alt.
Problemet med native moduler i Jest
Jest kører i et Node.js-miljø, ikke på en rigtig enhed. Når din komponent importerer et native modul som expo-camera eller @react-native-async-storage/async-storage, kan Jest ikke udføre den native kode, fordi der ikke er et tilgængeligt iOS- eller Android-runtime-miljø.
Det får tests til at mislykkes med fejl som 'jest-environment-jsdom is not supported' eller 'Cannot read property of undefined'. Løsningen er at simulere native moduler — erstatte dem med JavaScript-baserede erstatninger, der opfører sig forudsigeligt i tests.
Manuelle simuleringer i mappen __mocks__
Du kan oprette en manuel simulering af et hvilket som helst modul ved at oprette en fil i mappen __mocks__ ved siden af node_modules. Jest bruger automatisk denne simulering, når modulet importeres i testfiler.
Opret f.eks. __mocks__/@react-native-async-storage/async-storage.js med en falsk implementering, der gemmer data i hukommelsen. Expo- og React Native-fællesskaberne vedligeholder officielle simuleringspakker til almindelige native moduler.
// __mocks__/@react-native-async-storage/async-storage.js
const storage = {};
export default {
getItem: jest.fn((key) => Promise.resolve(storage[key] ?? null)),
setItem: jest.fn((key, value) => {
storage[key] = value;
return Promise.resolve();
}),
removeItem: jest.fn((key) => {
delete storage[key];
return Promise.resolve();
}),
clear: jest.fn(() => {
Object.keys(storage).forEach((k) => delete storage[k]);
return Promise.resolve();
}),
getAllKeys: jest.fn(() => Promise.resolve(Object.keys(storage))),
};Brug af den officielle simuleringspakke
@react-native-async-storage/async-storage leveres med sin egen officielle Jest-simulering på @react-native-async-storage/async-storage/jest/async-storage-mock. Registrer den i din Jest-opsætningsfil, så du kan bruge den i alle tests uden at skrive simuleringen manuelt.
På samme måde simulerer react-native selv mange indbyggede komponenter, når du bruger react-native-Jest-forudindstillingen. Se dokumentationen for hvert bibliotek for at finde den anbefalede opsætning af Jest-simuleringen.
// jest.config.js
module.exports = {
preset: 'jest-expo',
moduleNameMapper: {
'@react-native-async-storage/async-storage':
'@react-native-async-storage/async-storage/jest/async-storage-mock',
},
setupFilesAfterFramework: [
'@testing-library/jest-native/extend-expect',
],
};Simulering af expo-location
Moduler fra navnerummet expo-* kræver ofte native tilladelser og GPS-hardware. Simuler dem med jest.mock() øverst i din testfil, så den returnerer kontrollerede falske værdier uden adgang til en enhed.
Returner opfyldte Promises fra asynkrone metoder, så de svarer til den virkelige API-struktur. Ved at kontrollere returværdierne kan du teste alle grene: hvad der sker, når tilladelsen gives eller afvises, eller når GPS'en returnerer bestemte koordinater.
jest.mock('expo-location', () => ({
requestForegroundPermissionsAsync: jest.fn(),
getCurrentPositionAsync: jest.fn(),
PermissionStatus: { GRANTED: 'granted', DENIED: 'denied' },
}));
import * as Location from 'expo-location';
it('shows coordinates when permission is granted', async () => {
Location.requestForegroundPermissionsAsync
.mockResolvedValue({ status: 'granted' });
Location.getCurrentPositionAsync
.mockResolvedValue({ coords: { latitude: 37.78, longitude: -122.43 } });
const { findByText } = render(<LocationScreen />);
await findByText('37.78, -122.43');
});Simulering af Fetch API
Når komponenter foretager HTTP-anmodninger med fetch, skal du simulere den globale fetch-funktion i dine tests, så netværksanmodninger ikke rammer rigtige servere. Brug jest.spyOn(global, 'fetch').mockResolvedValue() til at erstatte fetch med en kontrolleret implementering.
Returner et simuleret Response-lignende objekt med en json()-metode, der returnerer dine testdata. Nulstil simuleringen i afterEach, så hver test starter med en ren tilstand.
beforeEach(() => {
global.fetch = jest.fn();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('displays fetched posts in a list', async () => {
const mockPosts = [{ id: '1', title: 'Hello World' }];
global.fetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve(mockPosts),
});
const { findByText } = render(<PostFeed />);
await findByText('Hello World');
expect(global.fetch).toHaveBeenCalledWith(
'https://api.example.com/posts'
);
});Simulering af Axios
Hvis din app bruger Axios, skal du installere axios-mock-adapter for at opfange anmodninger og returnere simulerede svar. Pak din Axios-instans ind i adapteren, definér simulerede svar pr. slutpunkt, og nulstil adapteren mellem tests.
Denne tilgang er renere end at simulere hele axios-modulet, fordi den lader dig teste bestemte URL-mønstre og HTTP-metoder uafhængigt af hinanden.
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
const mock = new MockAdapter(axios);
afterEach(() => mock.reset());
it('fetches and displays user profile', async () => {
mock.onGet('/api/users/123').reply(200, {
id: '123',
name: 'Alice',
email: 'alice@test.com',
});
const { findByText } = render(<ProfileScreen userId='123' />);
await findByText('Alice');
await findByText('alice@test.com');
});Test af asynkrone hooks med renderHook
RNTL eksporterer hjælpeværktøjet renderHook til at teste brugerdefinerede hooks isoleret. Det gengiver en minimal komponent, der kalder hooken, og gør hookens returværdi tilgængelig. Det lader dig teste hook-logik uden at koble den til en bestemt brugergrænsefladekomponent.
For asynkrone hooks, der henter data, skal du pakke tilstandsændringer ind i act og bruge waitFor til at vente på, at den asynkrone handling er færdig, før du foretager kontroller.
import { renderHook, act, waitFor } from '@testing-library/react-native';
import { useUserProfile } from '../src/hooks/useUserProfile';
it('fetches the user profile on mount', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: '1', name: 'Bob' }),
});
const { result } = renderHook(() => useUserProfile('1'));
// Initially loading
expect(result.current.loading).toBe(true);
// Wait for the fetch to complete
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.user?.name).toBe('Bob');
});Test af AsyncStorage
Når du tester komponenter, der læser fra eller skriver til AsyncStorage, skal du kontrollere, hvad lageret returnerer, så du kan teste forskellige starttilstande. Simuler lageret før testen, og kontrollér skrivekald efter en interaktion.
Ryd historikken over simuleringskald i beforeEach med jest.clearAllMocks(), så lagerlæsninger fra én test ikke påvirker den næste. Nulstil det lagerkort, der ligger i hukommelsen, hvis din manuelle simulering gemmer data på tværs af kald.
import AsyncStorage from '@react-native-async-storage/async-storage';
it('loads dark mode preference on mount', async () => {
// Arrange: storage returns 'dark' for theme key
AsyncStorage.getItem.mockResolvedValue('dark');
const { findByText } = render(<SettingsScreen />);
// Assert theme applied
await findByText('Dark Mode: ON');
expect(AsyncStorage.getItem).toHaveBeenCalledWith('theme');
});
it('saves the new theme when toggle is pressed', async () => {
AsyncStorage.getItem.mockResolvedValue('light');
const { getByTestId, waitFor } = render(<SettingsScreen />);
fireEvent(getByTestId('theme-toggle'), 'valueChange', true);
await waitFor(() =>
expect(AsyncStorage.setItem).toHaveBeenCalledWith('theme', 'dark')
);
});Test med waitFor og findBy
waitFor(callback) kalder gentagne gange callback-funktionen, indtil den ikke længere kaster en fejl, op til en konfigurerbar timeout (standardværdien er 1000 ms). Brug den, når brugergrænsefladen opdateres efter en asynkron handling, hvis tidspunkt ikke kan forudsiges.
findBy*-forespørgsler (som findByText) er syntaktisk sukker for waitFor(() => getByText(...)). Brug findBy til enkelte elementer og waitFor til komplekse kontroller, der involverer flere elementer eller betingelser.
import { render, fireEvent, waitFor } from '@testing-library/react-native';
it('shows error message when API returns 400', async () => {
global.fetch = jest.fn().mockResolvedValue({
ok: false,
status: 400,
json: () => Promise.resolve({ message: 'Invalid email' }),
});
const { getByRole, findByText } = render(<SignUpForm />);
fireEvent.press(getByRole('button', { name: 'Create Account' }));
// Wait for the error to appear after the API response
expect(await findByText('Invalid email')).toBeTruthy();
// Alternatively:
await waitFor(() => {
expect(getByText('Invalid email')).toBeTruthy();
expect(getByRole('button', { name: 'Create Account' })).not.toBeDisabled();
});
});Simulering af timere til forsinkede input
Søgeinput forsinker ofte API-kald — de venter på, at brugeren stopper med at skrive, før en anmodning udløses. Brug jest.useFakeTimers(), og styr tiden med jest.advanceTimersByTime(ms) i stedet for at vente på den virkelige forsinkelse for at teste denne adfærd.
Kald jest.useRealTimers() i afterEach for at gendanne de virkelige timerfunktioner til andre tests, der muligvis er afhængige af dem.
it('triggers search after 500ms debounce', async () => {
jest.useFakeTimers();
const mockSearch = jest.fn().mockResolvedValue([]);
const { getByPlaceholderText } = render(<DebouncedSearch onSearch={mockSearch} />);
fireEvent.changeText(getByPlaceholderText('Search...'), 'react');
// Not yet called — debounce delay not elapsed
expect(mockSearch).not.toHaveBeenCalled();
// Advance time past the debounce window
await act(async () => {
jest.advanceTimersByTime(500);
});
expect(mockSearch).toHaveBeenCalledWith('react');
jest.useRealTimers();
});Testopsætningsfiler til globale simuleringer
I stedet for at gentage den samme opsætning af simuleringer i hver testfil skal du placere globale simuleringer i en Jest-opsætningsfil, der er konfigureret med setupFilesAfterFramework. Den køres én gang før alle tests og registrerer simuleringerne globalt.
Almindelige globale simuleringer omfatter native moduler (AsyncStorage, kamera, placering), analysebiblioteker (for at forhindre netværkskald) og ydeevneovervågning. Hold opsætningsfilen minimal — tilføj kun simuleringer, der er nødvendige i de fleste tests.
// jest-setup.ts (referenced in jest.config.js setupFilesAfterFramework)
import '@testing-library/jest-native/extend-expect';
// Mock AsyncStorage globally
jest.mock('@react-native-async-storage/async-storage',
() => require('@react-native-async-storage/async-storage/jest/async-storage-mock')
);
// Mock analytics to avoid network calls in tests
jest.mock('@segment/analytics-react-native', () => ({
createClient: jest.fn(() => ({ track: jest.fn(), identify: jest.fn() })),
}));
// Silence console.error for expected React warnings in tests
const consoleError = console.error;
console.error = (...args) => {
if (String(args[0]).includes('Warning:')) return;
consoleError(...args);
};Hurtigt tjek
Test din forståelse af begreberne inden for React Native-mobiludvikling fra denne lektion.
Opsummering af lektionen
I denne lektion lærte du: hvordan du simulerer native moduler med jest.mock() og manuelle simuleringsfiler i mappen __mocks__, hvordan du simulerer HTTP-kald med jest.fn() eller axios-mock-adapter for at teste netværk kontrolleret, og hvordan du tester asynkrone forløb med waitFor, findBy-forespørgsler og falske timere. Som det næste skriver vi end-to-end-tests med Maestro, der styrer den virkelige app på en enhed.
Lær JavaScript med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 30
- Lektioner
- 120
Ofte stillede spørgsmål
Er lektionen “Mocking af native moduler og asynkron kode” gratis?
Ja — alle 3 lektioner i læringssporet React Native Academy, inklusive “Mocking af native moduler og asynkron kode”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. React Native Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Mocking af native moduler og asynkron kode”?
Mock expo-location og andre native moduler i Jest-opsætningsfiler, brug jest.fn() til callbacks, og test asynkrone flows med waitFor- og findBy-forespørgsler. Du øver dig i React Native Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på React Native Academy?
Der kræves ingen tidligere erfaring. React Native Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.
Hvor lang tid tager lektionen “Mocking af native moduler og asynkron kode”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne React Native Academy-lektion?
Ja. Alle React Native Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Opsætning af Jest i et React Native-projekt
- Gengivelse af komponenter med React Native Testing Library
- Udløsning af hændelser og test af brugerinteraktion
- Mocking af native moduler og asynkron kode