React Native Academy · Pelajaran

Memalsukan Modul Natif dan Kod Tak Segerak

Palsukan expo-location dan modul natif lain dalam fail persediaan Jest, gunakan jest.fn() untuk panggilan balik, dan uji aliran tak segerak dengan pertanyaan waitFor dan findBy.

Pelajaran 4 daripada 413 langkah

Memalsukan Modul Natif dan Kod Tak Segerak ialah pelajaran React Native Academy percuma di CoddyKit. Ini ialah pelajaran 4 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran React Native Academy, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus React Native Academy merangkumi sejumlah 4 pelajaran.

Masalah Modul Natif dalam Jest

Jest berjalan dalam persekitaran Node.js, bukan pada peranti sebenar. Apabila komponen anda mengimport modul natif seperti expo-camera atau @react-native-async-storage/async-storage, Jest tidak dapat melaksanakan kod natif kerana tiada persekitaran masa jalan iOS atau Android yang tersedia.

Hal ini menyebabkan ujian gagal dengan ralat seperti 'jest-environment-jsdom is not supported' atau 'Cannot read property of undefined'. Penyelesaiannya ialah melakukan mock modul natif — menggantikannya dengan tiruan yang hanya menggunakan JavaScript dan berkelakuan secara boleh dijangka dalam ujian.

Mock Manual dalam Folder __mocks__

Anda boleh mencipta mock manual untuk mana-mana modul dengan mencipta fail dalam direktori __mocks__ yang bersebelahan dengan node_modules. Jest akan menggunakan mock ini secara automatik setiap kali modul diimport dalam fail ujian.

Contohnya, cipta __mocks__/@react-native-async-storage/async-storage.js dengan pelaksanaan tiruan yang menyimpan data dalam memori. Komuniti Expo dan React Native menyelenggara pakej mock rasmi untuk modul natif yang biasa digunakan.

// __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))),
};

Menggunakan Pakej Mock Rasmi

@react-native-async-storage/async-storage disertakan dengan mock Jest rasmi sendiri di @react-native-async-storage/async-storage/jest/async-storage-mock. Daftarkannya dalam fail persediaan Jest anda untuk menggunakannya dalam semua ujian tanpa menulis mock secara manual.

Begitu juga, react-native sendiri melakukan mock terhadap banyak komponen terbina dalam apabila menggunakan pratetap Jest react-native. Semak dokumentasi setiap pustaka untuk persediaan mock Jest yang disyorkan.

// 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',
  ],
};

Melakukan Mock expo-location

Modul daripada ruang nama expo-* selalunya memerlukan kebenaran natif dan perkakasan GPS. Lakukan mock terhadapnya dengan jest.mock() di bahagian atas fail ujian anda untuk mengembalikan nilai tiruan yang terkawal tanpa akses kepada peranti.

Kembalikan Promise yang telah diselesaikan daripada kaedah tak segerak supaya bentuk API sebenar dapat dipadankan. Dengan mengawal nilai pulangan, anda boleh menguji semua cabang: perkara yang berlaku apabila kebenaran diberikan, ditolak atau apabila GPS mengembalikan koordinat tertentu.

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');
});

Melakukan Mock API Fetch

Apabila komponen membuat permintaan HTTP dengan fetch, lakukan mock terhadap fungsi global fetch dalam ujian anda supaya permintaan rangkaian tidak dihantar ke pelayan sebenar. Gunakan jest.spyOn(global, 'fetch').mockResolvedValue() untuk menggantikan fetch dengan pelaksanaan yang terkawal.

Kembalikan objek menyerupai mock Response dengan kaedah json() yang mengembalikan data ujian anda. Kosongkan mock dalam afterEach supaya setiap ujian bermula dalam keadaan bersih.

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'
  );
});

Melakukan Mock Axios

Jika aplikasi anda menggunakan Axios, pasang axios-mock-adapter untuk memintas permintaan dan mengembalikan respons mock. Bungkus tika Axios anda dalam penyesuai, takrifkan respons mock bagi setiap titik akhir dan tetapkan semula penyesuai antara ujian.

Pendekatan ini lebih kemas berbanding melakukan mock terhadap keseluruhan modul axios kerana anda boleh menguji corak URL dan kaedah HTTP tertentu secara berasingan.

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');
});

Menguji Cangkuk Tak Segerak dengan renderHook

RNTL mengeksport utiliti renderHook untuk menguji cangkuk tersuai secara berasingan. Utiliti ini merender komponen minimum yang memanggil cangkuk tersebut dan mendedahkan nilai pulangan cangkuk. Dengan ini, anda boleh menguji logik cangkuk tanpa mengikatnya pada komponen UI tertentu.

Untuk cangkuk tak segerak yang mengambil data, bungkus perubahan keadaan dalam act dan gunakan waitFor untuk menunggu operasi tak segerak selesai sebelum membuat penegasan.

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');
});

Melakukan Mock AsyncStorage dalam Ujian

Apabila menguji komponen yang membaca atau menulis kepada AsyncStorage, anda perlu mengawal perkara yang dikembalikan oleh storan untuk menguji keadaan awal yang berbeza. Lakukan mock terhadap storan sebelum ujian dan sahkan panggilan tulis selepas interaksi.

Kosongkan sejarah panggilan mock dalam beforeEach dengan jest.clearAllMocks() supaya bacaan storan daripada satu ujian tidak mempengaruhi ujian seterusnya. Tetapkan semula peta storan dalam memori jika mock manual anda menyimpan data merentas panggilan.

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')
  );
});

Menguji dengan waitFor dan findBy

waitFor(callback) memanggil semula callback sehingga callback itu tidak lagi menghasilkan ralat, sehingga had masa boleh dikonfigurasikan (lalai 1000ms). Gunakannya apabila kemas kini UI berlaku selepas operasi tak segerak yang masanya tidak dapat dijangka.

Pertanyaan findBy* (seperti findByText) ialah gula sintaks untuk waitFor(() => getByText(...)). Gunakan findBy untuk elemen tunggal dan waitFor untuk penegasan kompleks yang melibatkan beberapa elemen atau syarat.

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();
  });
});

Melakukan Mock Pemasa untuk Input Debun

Input carian sering melakukan debun terhadap panggilan API — input tersebut menunggu pengguna berhenti menaip sebelum mencetuskan permintaan. Untuk menguji tingkah laku debun, gunakan jest.useFakeTimers() dan kawal masa dengan jest.advanceTimersByTime(ms) dan bukannya menunggu tempoh debun sebenar.

Panggil jest.useRealTimers() dalam afterEach untuk memulihkan fungsi pemasa sebenar bagi ujian lain yang mungkin bergantung padanya.

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();
});

Fail Persediaan Ujian untuk Mock Global

Daripada mengulangi persediaan mock yang sama dalam setiap fail ujian, letakkan mock global dalam fail persediaan Jest yang dikonfigurasikan dengan setupFilesAfterFramework. Fail ini dijalankan sekali sebelum semua ujian dan mendaftarkan mock secara global.

Mock global yang biasa termasuk modul natif (AsyncStorage, kamera, lokasi), pustaka analitik (untuk menghalang panggilan rangkaian) dan pemantauan prestasi. Pastikan fail persediaan minimum — hanya tambahkan mock yang diperlukan dalam kebanyakan ujian.

// 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);
};

Semakan Pantas

Uji pemahaman anda tentang konsep Pembangunan Mudah Alih React Native daripada pelajaran ini.

Imbas Kembali Pelajaran

Dalam pelajaran ini, anda mempelajari: cara melakukan mock modul natif menggunakan jest.mock() dan fail mock manual dalam direktori __mocks__, cara melakukan mock panggilan HTTP dengan jest.fn() atau axios-mock-adapter untuk ujian rangkaian terkawal, serta cara menguji aliran tak segerak dengan pertanyaan waitFor, findBy dan pemasa palsu. Seterusnya, kita akan menulis ujian hujung ke hujung dengan Maestro yang mengawal aplikasi sebenar pada peranti.

Percuma untuk bermula

Pelajari JavaScript dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
30
Pelajaran
120

Soalan Lazim

Adakah pelajaran “Memalsukan Modul Natif dan Kod Tak Segerak” percuma?

Ya — sebanyak 3 pelajaran dalam laluan pembelajaran React Native Academy, termasuk “Memalsukan Modul Natif dan Kod Tak Segerak”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus React Native Academy merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Memalsukan Modul Natif dan Kod Tak Segerak”?

Palsukan expo-location dan modul natif lain dalam fail persediaan Jest, gunakan jest.fn() untuk panggilan balik, dan uji aliran tak segerak dengan pertanyaan waitFor dan findBy. Anda berlatih React Native Academy menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan React Native Academy?

Tiada pengalaman terdahulu diperlukan. Pembelajaran React Native Academy di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 4 daripada 4.

Berapa lamakah pelajaran “Memalsukan Modul Natif dan Kod Tak Segerak” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran React Native Academy ini?

Ya. Setiap pelajaran React Native Academy menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Menyediakan Jest dalam Projek React Native
  2. Memaparkan Komponen dengan React Native Testing Library
  3. Mencetuskan Peristiwa dan Menguji Interaksi Pengguna
  4. Memalsukan Modul Natif dan Kod Tak Segerak
← Kembali ke React Native Academy