การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส
จำลอง expo-location และโมดูลเนทีฟอื่นในไฟล์ตั้งค่า Jest ใช้ jest.fn() สำหรับตัวเรียกกลับ และทดสอบลำดับการทำงานแบบอะซิงโครนัสด้วย waitFor และคำค้น findBy
การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส เป็นบทเรียน React Native Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน React Native Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน
บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ
The Native Module Problem in Jest
Jest runs in a Node.js environment, not on a real device. When your component imports a native module like expo-camera or @react-native-async-storage/async-storage, Jest cannot execute the native code because there is no iOS or Android runtime available.
This causes tests to fail with errors like 'jest-environment-jsdom is not supported' or 'Cannot read property of undefined'. The solution is to mock native modules — replace them with JavaScript-only fakes that behave predictably in tests.
Manual Mocks in __mocks__ Folder
You can create a manual mock for any module by creating a file in the __mocks__ directory adjacent to node_modules. Jest automatically uses this mock whenever the module is imported in test files.
For example, create __mocks__/@react-native-async-storage/async-storage.js with a fake implementation that stores data in memory. The Expo and React Native communities maintain official mock packages for common native modules.
// __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))),
};Using the Official Mock Package
@react-native-async-storage/async-storage ships its own official Jest mock at @react-native-async-storage/async-storage/jest/async-storage-mock. Register it in your Jest setup file to use it across all tests without manually writing the mock.
Similarly, react-native itself mocks many built-in components when using the react-native Jest preset. Check each library's documentation for their recommended Jest mock setup.
// 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',
],
};Mocking expo-location
Modules from the expo-* namespace often require native permissions and GPS hardware. Mock them with jest.mock() at the top of your test file to return controlled fake values without device access.
Return resolved Promises from async methods to match the real API shape. By controlling the return values, you can test all branches: what happens when permission is granted, denied, or when GPS returns specific coordinates.
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');
});Mocking the Fetch API
When components make HTTP requests with fetch, mock the global fetch function in your tests so network requests do not hit real servers. Use jest.spyOn(global, 'fetch').mockResolvedValue() to replace fetch with a controlled implementation.
Return a mock Response-like object with a json() method that returns your test data. Reset the mock in afterEach so each test starts clean.
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'
);
});Mocking Axios
If your app uses Axios, install axios-mock-adapter to intercept requests and return mock responses. Wrap your Axios instance in the adapter, define mock responses per endpoint, and reset the adapter between tests.
This approach is cleaner than mocking the entire axios module because it lets you test specific URL patterns and HTTP methods independently.
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');
});Testing Async Hooks with renderHook
RNTL exports a renderHook utility for testing custom hooks in isolation. It renders a minimal component that calls the hook and exposes the hook's return value. This lets you test hook logic without coupling it to a specific UI component.
For async hooks that fetch data, wrap state changes in act and use waitFor to wait for the async operation to complete before asserting.
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');
});Mocking AsyncStorage in Tests
When testing components that read or write to AsyncStorage, you need to control what the storage returns to test different initial states. Mock the storage before the test and verify the write calls after an interaction.
Clear mock call history in beforeEach with jest.clearAllMocks() so storage reads from one test do not bleed into the next. Reset the in-memory storage map if your manual mock stores data across calls.
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')
);
});Testing with waitFor and findBy
waitFor(callback) repeatedly calls the callback until it does not throw, up to a configurable timeout (default 1000ms). Use it when the UI update happens after an async operation that does not have a predictable timing.
findBy* queries (like findByText) are syntactic sugar for waitFor(() => getByText(...)). Use findBy for single elements and waitFor for complex assertions involving multiple elements or conditions.
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();
});
});Mocking timers for Debounced Inputs
Search inputs often debounce API calls — they wait for the user to stop typing before triggering a request. To test debounced behavior, use jest.useFakeTimers() and control time with jest.advanceTimersByTime(ms) instead of waiting for the real debounce delay.
Call jest.useRealTimers() in afterEach to restore the real timer functions for other tests that might rely on them.
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();
});Test Setup Files for Global Mocks
Instead of repeating the same mock setup in every test file, put global mocks in a Jest setup file configured with setupFilesAfterFramework. This runs once before all tests and registers mocks globally.
Common global mocks include native modules (AsyncStorage, camera, location), analytics libraries (to prevent network calls), and performance monitoring. Keep the setup file minimal — only add mocks that are needed in most 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);
};Quick Check
Test your understanding of React Native Mobile Development concepts from this lesson.
Lesson Recap
In this lesson you learned: how to mock native modules using jest.mock() and manual mock files in the __mocks__ directory, how to mock HTTP calls with jest.fn() or axios-mock-adapter for controlled network testing, and how to test async flows with waitFor, findBy queries, and fake timers. Next up we write end-to-end tests with Maestro that drive the real app on a device.
คำถามที่พบบ่อย
บทเรียน “การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส React Native Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส”
จำลอง expo-location และโมดูลเนทีฟอื่นในไฟล์ตั้งค่า Jest ใช้ jest.fn() สำหรับตัวเรียกกลับ และทดสอบลำดับการทำงานแบบอะซิงโครนัสด้วย waitFor และคำค้น findBy คุณปฏิบัติ React Native Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน React Native Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน React Native Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน
บทเรียน “การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน React Native Academy นี้ได้ไหม
ได้ บทเรียน React Native Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- การตั้งค่า Jest ในโปรเจกต์ React Native
- การเรนเดอร์ส่วนประกอบด้วย React Native Testing Library
- การส่งเหตุการณ์และการทดสอบการโต้ตอบของผู้ใช้
- การจำลองโมดูลเนทีฟและโค้ดแบบอะซิงโครนัส