Context
Our team just inherited a React-Native (not Expo), Typescript codebase without any tests for the TSX. We are currently trying to setup the first set of tests for some hooks, but are unable to run them. Other unit tests that do not involve TSX and react-native dependencies are running without any problems.
We receive the following error:
Details: /Users/<username>/Repos/<client>/<project>/clients/mobile-app/node_modules/launchdarkly-react-native-client-sdk/index.js:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; ^^^^^^ SyntaxError: Cannot use import statement outside a module 1 | import React, { useEffect, useState, useContext } from 'react';> 2 | import LDClient, { | ^ 3 | LDUserConfig, 4 | LDClientConfig, 5 | } from 'launchdarkly-react-native-client-sdk'; at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1479:14) at Object.<anonymous> (src/feature-flags.tsx:2:1)
From what I read about this topic and the error, it seems likely that the dependency launchdarkly-react-native-client-sdk
is not transpiled, since the node_modules
are by default not transformed by jest. There is a lot of information around this topic and I have tried a multitude of proposed solutions, but I wasn't able to make anything work. I am quite sure that I am overlooking something simple, but any help would be greatly appreciated.
Code
package.json - jest config
"jest": {"preset": "react-native","moduleFileExtensions": ["ts","tsx","js","jsx","json","node" ] }
babel.config.js
module.exports = { presets: ['module:metro-react-native-babel-preset'], plugins: ['relay', 'transform-inline-environment-variables'],};
feature-flags.test.tsx
import React from 'react';import { renderHook } from '@testing-library/react-hooks';import { FeatureFlagContext, useBooleanFlag } from './feature-flags';import LDClient from 'launchdarkly-react-native-client-sdk';const wrapper = (client: LDClient | null): React.FC => ({ children }) => { return (<FeatureFlagContext.Provider value={{ client, userIdentified: false }}> {children}</FeatureFlagContext.Provider> ); };describe('useBooleanFlag', () => { test('if client is null (iOS), return loading state', () => { const { result } = renderHook(() => useBooleanFlag('competitions'), { wrapper: wrapper(null), }); expect(result.current.isLoading).toBe(true); });});
Tried Solutions
Unfortunately, the error seems to be quite generic which makes doing research on a fix a bit more cumbersome. We have tried the following:
- Explicitly transform LaunchDarkly dependency. Not sure whether the pattern actually works, was only able to test it here and did not find a way to verify it locally. Returns the same error.
package.json
"jest": {<same as first package.json>"transform": {"^.+\\/(launchdarkly-react-native-client-sdk)\\/.+$": "babel-jest" } }
- Explicitly transform everything. Returns the same error.
"jest": {<same as first package.json>"transform": {"^.+\\.(js|ts)x?$": "babel-jest" } }
- Explicitly transform everything but exclude
node_modules
. Breaks all tests.
"jest": {<same as first package.json>"transform": {"^.+\\.(js|ts)x?$": "babel-jest" }"transformIgnorePatterns": ["node_modules/(?!(jest-)?react-native|<other packages>)" ] }
- Adjust babel.config.js
presets: ['module:metro-react-native-babel-preset','@babel/preset-env', { modules: 'auto' }, ], plugins: ['relay', 'transform-inline-environment-variables'],};
Only include the transformIgnorePatterns based on this solution. Results in all tests failing.
Only ignore package name via the CLI (as a sanity check)
yarn test --transformIgnorePatterns 'node_modules/(?react-native-client-sdk)/'
Results in a different error. Suggested here.
Invariant Violation: Native module cannot be null. at invariant (node_modules/invariant/invariant.js:40:15) at new NativeEventEmitter (node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter.js:49:7) at RNFBNativeEventEmitter._createSuperInternal (node_modules/@react-native-firebase/app/lib/internal/RNFBNativeEventEmitter.js:22:311) at new RNFBNativeEventEmitter (node_modules/@react-native-firebase/app/lib/internal/RNFBNativeEventEmitter.js:24:5) at Object.<anonymous> (node_modules/@react-native-firebase/app/lib/internal/RNFBNativeEventEmitter.js:75:16) at Object.<anonymous> (node_modules/@react-native-firebase/app/lib/internal/registry/nativeModule.js:21:1)
Which seems logical.
Conclusion
I am completely lost as you can see. Configuring these things is something I know very little about and therefore I decided to try things that seemingly seem counter productive. Any help would be deeply appreciated.