Jest runs in Node - there is no camera, GPS, or Keychain. jest-expo stubs most Expo SDK modules with safe defaults; you add manual mocks when your tests need specific permission results, sensor readings, or third-party native SDK behavior.
jest-expo preset installs default mocks for Expo modules so imports do not crash Jest. Defaults return resolved promises with generic "success" shapes - rarely matching your production edge cases.
jest.mock("module-name") hoists to the top of the file and swaps the module before your imports evaluate. Works for expo-*, @react-native-async-storage/async-storage, and app-local modules.
Manual mocks in __mocks__/ next to node_modules package name or src/ file apply when you call jest.mock without a factory.
Spy pattern - jest.spyOn(module, "fn") wraps a single function while leaving the rest real (when not fully mocked).
Boundary mocking - mock src/services/api.ts, not fetch and not every transitive import, unless testing the HTTP client itself.
Treat Jest native mocks as contract tests for your JS reaction - not proof the native bridge works. Add Maestro E2E or manual QA for permission dialogs and hardware paths.
Assuming jest-expo defaults match your test - Default permission mocks often return granted. Tests never exercise denial paths unless you override. Fix: Set mockResolvedValue for each permission/status scenario explicitly.
Mocking too deep - Mocking react-native internals or every expo-* import makes tests brittle when SDK updates changes signatures. Fix: Mock your service boundary; keep one module responsible for native calls.
Forgetting jest.clearAllMocks() - Call counts and queued implementations leak across tests. Fix:afterEach in jest.setup.ts or beforeEach in describe blocks that use spies.
jest.requireActual after full mock - Spreading requireActual for native modules can pull real native bindings into Jest and crash. Fix: Mock only the functions you call; avoid requireActual on modules that touch native code.
False confidence from green suites - All native behavior faked; production still fails on a real device. Fix: Document which flows need Maestro or manual device checks in PR templates.
Dynamic import() bypassing mocks - Lazy imports may resolve before jest.mock applies if mis-ordered. Fix: Use static imports in code under test, or jest.unstable_mockModule for ESM dynamic import paths.
Duplicating mock setup in every file - Copy-pasted expo-font mocks drift. Fix: Shared helpers in test-utils/native-mocks.ts or global mocks in jest.setup.ts for app-wide modules (fonts, splash screen).
It mocks the majority of expo-* packages shipped with the SDK. Custom native modules, bare third-party SDKs, and some community packages are not covered - add jest.mock manually.
The package ships an official Jest mock - prefer it over hand-rolled key-value objects.
How do I mock expo-router?
Prefer renderRouter from expo-router/testing-library over mocking useRouter when testing navigation. For leaf components, pass navigation callbacks as props instead of mocking the router hook.
Use sparingly - requireActual on native-heavy modules can fail; a thin wrapper module is safer.
Why does my mock not apply?
jest.mock must be at module scope (hoisted), not inside it/beforeEach.
Ensure the import path matches exactly what production code imports.
Clear Metro/Jest cache: npx jest --clearCache.
Should I mock fetch or the API client?
Mock src/api/client or the TanStack Query queryFn wrapper - not both. One boundary keeps tests focused on how the app handles success and error responses.
Match the shape from the real Expo module's TypeScript types - incomplete objects cause false positives.
What is false confidence?
Tests pass because mocks always return success, while real users hit denial dialogs, airplane mode, or OS bugs. Complement Jest with Maestro E2E for permission and OS-integration paths.
How do I test a custom Expo native module?
Create a TypeScript facade that imports your module. In Jest, jest.mock("@/modules/my-native-module") with a manual mock returning the JS API your app expects. Validate the real module on device builds.
Do mocks belong in setupFilesAfterEnv?
Only for modules every test needs (fonts, splash, reanimated silence). Feature mocks belong next to the tests that assert on them - global mocks hide missing setup in new tests.
How do I reset mocks between tests?
afterEach(() => { jest.clearAllMocks(); // clears call history // jest.resetAllMocks(); // also resets implementations - use when tests override mockResolvedValue});
Add to jest.setup.ts for project-wide consistency.