Mocking native calls in Expo modules

Edit page

Learn about mocking native calls in Expo modules.


The recommended way to write unit tests for an Expo project is to use Jest and the jest-expo preset.

To write a unit test for an app that uses native code, you need to mock native calls. The term mocking means to replace the actual implementation of a function with a fake version that does not perform any actions. This approach is useful for running unit tests on a local computer, as it involves bypassing the need for native code, which can only run on an actual Android or iOS device.

Expo SDK includes a set of default mocks for each of our community packages. You can also mock any JS code yourself using built-in Jest APIs such as mock functions.

However, to provide default mocks in your Expo Module, we offer a method to bundle them. This ensures that when your module user runs unit tests, they will automatically use a mocked implementation.

Providing mocks for a module

Create a file with the same name as the native module you want to mock and place it in your module's mocks directory. Make sure to export the mock implementation from this file. The jest-expo preset will automatically return the exported functions because of a requireNativeModule call when running during a unit test.

For example, the expo-clipboard library has a native module called ExpoClipboard. You will create a ExpoClipboard.ts in the mocks directory to mock it.

ExpoClipboard.ts
export async function hasStringAsync(): Promise<boolean> { return false; }

Now, in a unit test, calling ExpoClipboard.hasStringAsync() returns false.

Automatic generation of mocks

Maintaining mocks for native modules can be a lot of work if the native module has multiple methods. To make this easier, we provide a script that automatically generates mocks for all native functions in a module's mocks directory. It works for generating mocks in TypeScript and JavaScript based on the Swift implementation in your module. Methods that exist only on Android (for example, Kotlin-only APIs) will not be generated automatically. In those cases, manually add or adjust the mock in the mocks directory.

To use this script, you have to install SourceKitten framework. Then, navigate to the module directory (where your module's expo-module.config.json is located) and run the generate-ts-mocks command.

Terminal
- brew install sourcekitten
- npx expo-modules-test-core generate-ts-mocks

The command above generates ExpoModuleName.ts in the mocks directory of your module. It contains a mock implementation for each native method and view in your module.

Tip: You can also run generate-js-mocks to generate mocks in JavaScript.

Unit testing with mocked modules

Once you have created mocks for your native modules, you can write comprehensive unit tests to verify that your JavaScript code calls the native functions correctly and handles their responses appropriately. For example, running npx expo-modules-test-core generate-ts-mocks command, will generate a mock similar to the example shown below inside example-module/mocks directory:

example-module/mocks/ExpoModuleName.ts
/** * Automatically generated by expo-modules-test-core. * * This autogenerated file provides a mock for native Expo module, * and works out of the box with the expo jest preset. * */ export type URL = any; export function hello(): any {} export async function setValueAsync(value: string): Promise<any> {} export type ViewProps = { url: URL; onLoad: (event: any) => void; }; export function View(props: ViewProps) {}

The examples in the following sections demonstrate comprehensive unit testing patterns using real testing techniques from Expo SDK modules such as expo-clipboard, expo-screen-capture, and expo-app-integrity.

Basic test setup

Create test files in a __tests__ directory next to your source files. Import your module and the mocked native module to write assertions:

MyModule.test.js
import * as MyModule from '../MyModule'; import ExpoMyModule from '../ExpoMyModule'; describe('MyModule', () => { it('calls native module with correct parameters', async () => { await MyModule.doSomething('test-param'); expect(ExpoMyModule.doSomething).toHaveBeenCalledWith('test-param'); }); });

Testing function calls and return values

Use Jest's mock assertion methods to verify that your JavaScript functions delegate to native implementations correctly:

MyModule.test.js
describe('Module functionality', () => { it('delegates to native implementation', () => { MyModule.setData('test-data'); expect(ExpoMyModule.setDataAsync).toHaveBeenCalledWith('test-data', {}); }); it('handles async operations', async () => { await expect(MyModule.getDataAsync()).resolves.not.toThrow(); }); it('verifies call count', () => { MyModule.performAction(); MyModule.performAction(); expect(ExpoMyModule.performAction).toHaveBeenCalledTimes(2); }); });

Testing React hooks with native modules

When testing React hooks that use native modules, use React Testing Library's renderHook function:

useMyHook.test.js
import { renderHook } from '@testing-library/react-native'; import { useMyHook } from '../useMyHook'; import ExpoMyModule from '../ExpoMyModule'; jest.mock('../ExpoMyModule', () => ({ startOperation: jest.fn().mockResolvedValue(), stopOperation: jest.fn().mockResolvedValue(), })); describe('useMyHook', () => { it('calls native methods on mount and unmount', () => { const hook = renderHook(useMyHook); expect(ExpoMyModule.startOperation).toHaveBeenCalledTimes(1); hook.unmount(); expect(ExpoMyModule.stopOperation).toHaveBeenCalledTimes(1); }); it('handles parameter changes', () => { const hook = renderHook(useMyHook, { initialProps: 'param1' }); hook.rerender('param2'); expect(ExpoMyModule.startOperation).toHaveBeenCalledTimes(2); expect(ExpoMyModule.stopOperation).toHaveBeenCalledTimes(1); }); });

Best practices

  • Clean up between tests: Use beforeEach or afterEach to reset mocks and avoid test pollution.
  • Test edge cases: Verify behavior when native functions throw errors or return unexpected values.
  • Use descriptive test names: Write test descriptions that explain the specific behavior being verified.
  • Group related tests: Use describe blocks to organize tests by functionality or component.

More

Unit testing with Jest

Learn how to set up and configure the jest-expo package to write unit and snapshot tests for a project.