← Back to Blog

Native tests for iOS Expo modules

Published January 9, 2024 2 min read
iOSReact NativeExpoTestingSwiftObjective-C

Problem

You want to test the native code you’ve written for your React Native app using Expo modules.

Custom native functions are great candidates for unit testing (if you still haven’t started writing tests for them), as often they are separated from the view layer.

Simple approach

The simplest solution is to use the React Native Testing Library or other tools to test it directly from React Native.

But this approach introduces some additional issues you need to consider:

  • You test your native code with the whole communication bridge. I believe this is a good aspect, but it introduces additional points of failure.
  • You need to create additional bridging functions to thoroughly test your native code. Usually, it is a good practice to replace the chain of native calls with one call to reduce bridge overload, but in that case, you will either make a lot of bridge calls or introduce a hard-to-test function.
  • Sometimes it is a good idea to use the native testing stack. For iOS, you can use performance tests or snapshot testing.

Alternative approach

First, we want to create a prebuilt Expo app with the Expo module:

npx create-expo-app@latest my-app
cd my-app
npx expo prebuild --platform ios
npx create-expo-module@latest --local

Additionally, we need to open the Xcode project (yourmodule.xcodeproj -> pay attention to opening xcodeproj, not xcworkspace). Add a test target by using File -> New -> Target and then selecting Unit Testing Bundle. Note that changes made to .xcodeproj and Podfile will be removed after using npx expo prebuild --clean. Consider creating your own template or post-execution script to add them.

Then, we want to look at the podspec file. The most important change is adding s.test_spec, which points to the test directory as per CocoaPods test specs. Pay attention that test files and source files should not overlap, so it is best to keep them in separate folders. .podspec is the library configuration file for CocoaPods. In short, iOS has a dependency manager (and project generator) called CocoaPods, written in Ruby. Apple already offers SwiftPM as a replacement, but due to other architecture, CocoaPods is still a popular option.

To add tests to your Expo module, add this before the last end (assuming you have test files in the Tests folder):

  s.test_spec '<Module Name>Tests' do |test_spec|
    test_spec.source_files = 'Tests/*.{h,m,swift}'
  end

So your file should look like this (I’ve added Quick/Nimble dependencies for testing). It is good to use Quick/Nimble as it makes tests more similar to JavaScript test frameworks:

Pod::Spec.new do |s|
  s.name           = '<Module Name>'
  s.version        = '1.0.0'
  s.summary        = 'A sample project summary'
  s.description    = 'A sample project description'
  s.author         = ''
  s.homepage       = 'https://docs.expo.dev/modules/'
  s.platform       = :ios, '13.0'
  s.source         = { git: '' }
  s.static_framework = true

  s.dependency 'ExpoModulesCore'

  # Swift/Objective-C compatibility
  s.pod_target_xcconfig = {
    'DEFINES_MODULE' => 'YES',
    'SWIFT_COMPILATION_MODE' => 'wholemodule'
  }

  s.source_files = "*.{h,m,mm,swift,hpp,cpp}"

  s.test_spec '<Module Name>Tests' do |test_spec|
    test_spec.source_files = 'Tests/*.{h,m,swift}'
    test_spec.dependency 'Quick'
    test_spec.dependency 'Nimble'
  end
end

After we declare tests in our module, we want to link them to the main project to make them available in the Xcode project. To link tests, we need to open the Podfile and replace:

use_expo_modules!

With:

pod 'ExpoModulesTestCore', :path => "../node_modules/expo-modules-test-core/ios"
use_expo_modules!({ includeTests: true })

We need ExpoModulesTestCore as it is declared as a test dependency in standard Expo modules. We can install it by:

npx expo install expo-modules-test-core

Then install pods:

npx pod-install ios

This will import tests during CocoaPods installation and automatically create library test targets. Then you can access tests by using the left pane in Xcode.

How to verify it worked

The setup is working when the module test target appears in Xcode and running it produces a green test result.

Happy testing!