jest unexpected identifier when I add setup files - react-native

Why I get this error:
C:\PC\node_modules\#react-native\polyfills\error-guard.js:14
type ErrorHandler = (error: mixed, isFatal: boolean) => void;
^^^^^^^^^^^^
SyntaxError: Unexpected identifier
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1350:14)
at Object.<anonymous> (node_modules/react-native/jest/setup.js:16:6)
This comes when I add this line:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"],
I want to test my navigation
NavigationTest.js
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { render } from '#testing-library/react-native';
import { NativeStackWrapper } from '../src/navigation/CurvedBottomTab';
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');
describe('test the Navigation', () => {
it('should render the curved tab navigator without errors', () => {
const { debug } = render(
<NavigationContainer>
<NativeStackWrapper />
</NavigationContainer>)
debug();
});
});
Package.json
...
"jest": {
"preset": "jest-expo",
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"],
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)|(?!lodash-es)"
]
},

Related

Jest (ESM) can't import React Native components

Trying to use Jest with ESM configuration to test React Native components and getting errors that Jest can't parse the imports from 'react-native'. The errors look like this: SyntaxError: The requested module 'react-native' does not provide an export named 'StyleSheet'. Any idea what I'm doing wrong?
RN version: 0.70.4
Example component:
import React, { ReactElement } from 'react';
import { StyleSheet, Text, View } from 'react-native';
const { container } = StyleSheet.create({
container: {
padding: 50,
},
});
const ThisComponent = (): ReactElement => (
<View style={container}>
<Text>Hello World!</Text>
</View>
);
export default ThisComponent;
Example test:
import React, { ReactElement } from 'react';
import { render } from '#testing-library/react-native';
import ThisComponent from '../ThisComponent';
describe('<OTPInputs>', () => {
test('Renders without exploding', async () => {
const { getByText } = render(
<ThisComponent />,
);
expect(getByText('Hello World!')).not.toBeDisabled();
});
});
Jest config (in package.json):
"jest": {
"haste": {
"defaultPlatform": "ios",
"platforms": [
"android",
"ios",
"native"
]
},
"resetMocks": true,
"testEnvironment": "node",
"testMatch": [
"**/src/**/*.(spec|test).[tj]s?(x)"
],
"preset": "ts-jest/presets/default-esm",
"transform": {
"^.+\\.js$": "babel-jest"
},
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)/)"
],
"extensionsToTreatAsEsm": [
".ts",
".tsx"
],
"globals": {
"ts-jest": {
"useESM": true
}
},
"setupFiles": [
"<rootDir>/node_modules/react-native/jest/setup.js"
],
"setupFilesAfterEnv": [
"#testing-library/jest-native/extend-expect"
],
"moduleNameMapper": {
"^~/(.*)$": "<rootDir>/src/$1",
"^~components/(.*)$": "<rootDir>/src/components/$1",
"^~util/(.*)$": "<rootDir>/src/util/$1",
"^~types/(.*)$": "<rootDir>/src/types/$1"
}
}
EDIT: Looks like the main RN export is a cjs file with some weird syntax in it. Here is a mock that makes it work (with const ThisComponent = (await import('../ThisComponent').default):
jest.unstable_mockModule('react-native', () => ({
__esModule: true,
StyleSheet: jest.requireActual(
'react-native/Libraries/StyleSheet/StyleSheet',
),
Text: jest.requireActual('react-native/Libraries/Text/Text'),
TextInput: jest.requireActual(
'react-native/Libraries/Components/TextInput/TextInput',
),
TouchableOpacity: jest.requireActual(
'react-native/Libraries/Components/Touchable/TouchableOpacity',
),
View: jest.requireActual('react-native/Libraries/Components/View/View'),
}));
Your Jest config looks quite complex and I suspect that it might be the reason. In general importing from react-native package should work out of the box in when using React Native Testing Library.
In case of config issue its always a good idea to start with a working step and then add additional config entries you need. In RNTL we have a basic example app that is useful for that purpose.

How to import i18next in React Native?

I'm trying to use react-i18next in a React Native app. When importing i18next at the top of my index.js or app.tsx file, the build crashes with this error:
ERROR TypeError: undefined is not a function, js engine: hermes
ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native., js engine: hermes
Here is the code:
index.js:
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import "i18n/index";
AppRegistry.registerComponent(appName, () => App);
i18/index.ts:
import i18n, { LanguageDetectorAsyncModule } from "i18next";
import { initReactI18next } from "react-i18next";
import { Platform, NativeModules } from "react-native";
import { AsyncStorage } from "react-native";
import en from "./en.json";
import fr from "./fr.json";
const languages = {
EN: "en",
FR: "fr",
};
const resources = {
en: { translation: en },
fr: { translation: fr },
};
const detectLocale = () => {
const storedLocale = AsyncStorage.getItem("locale");
if (storedLocale) {
return storedLocale;
}
const mobileLocale =
Platform.OS === "ios"
? NativeModules.SettingsManager.settings.AppleLocale ||
NativeModules.SettingsManager.settings.AppleLanguages[0]
: NativeModules.I18nManager.localeIdentifier;
if (mobileLocale) {
return mobileLocale;
}
return languages.EN;
};
const LanguageDetector = {
type: "languageDetector" as LanguageDetectorAsyncModule["type"],
async: false,
init: () => {},
detect: detectLocale,
cacheUserLanguage: () => {},
};
export default i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: languages.EN,
keySeparator: ".",
whitelist: [languages.EN, languages.FR],
interpolation: { escapeValue: false },
});
How to fix this?
Try rerunning metro bundler and make sure there isn't another metro bundler running for another react native application.

Jest testing React-native component which uses NavigationEvents trows error

I'm having some troubles Jest testing a component which uses {NavigationEvents} from 'react-navigation' this is the part of the component where i use it:
render() {
const spinner = this.state.isLoading ? (
<ActivityIndicator size="large" />
) : null;
return (
<ScrollView
style={styles.container}
keyboardDismissMode="on-drag"
testID='SettingContainer'>
<NavigationEvents
onWillBlur={payload =>
locationGetter.checkLocationData(
payload,
'Settings',
this.props.t,
this.props.location,
'getPrayerPage',
)
}
/>
Now when i run this basic test :
import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';
import {I18nextProvider} from 'react-i18next';
import i18n from 'config/i18nForTest';
import {Settings} from 'containers/Settings'; // to get redux connected component import Setting without {}
// import configureStore from 'redux-mock-store';
// import { Provider } from 'react-redux';
// const mockStore = configureStore([]);
// const store = mockStore({ contacts: [ ] });
it('Does Settings renders correctly?', () => {
const tree = renderer
.create(
<Settings t={key => key} />,
)
.toJSON();
expect(tree).toMatchSnapshot();
});
i get this error :
● Does Settings renders correctly?
Invariant Violation: withNavigation can only be used on a view hierarchy of a navigator. The wrapped component is unable to get access to navigation from props or context.
at invariant (node_modules/#react-navigation/core/lib/commonjs/utils/invariant.js:41:20)
at node_modules/#react-navigation/core/lib/commonjs/views/withNavigation.js:22:15
at updateContextConsumer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7275:19)
at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7441:14)
at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10138:12)
at workLoop (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10170:24)
at renderRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10256:7)
at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11121:7)
at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11033:7)
at performSyncWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11007:3)
console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8060
The above error occurred in the <Context.Consumer> component:
in withNavigation(NavigationEvents) (at Settings.js:116)
in View (created by View)
in View (at ScrollViewMock.js:29)
in RCTScrollView (created by _class)
in _class (at ScrollViewMock.js:27)
in ScrollViewMock (at Settings.js:112)
in Settings (at setting.test.js:20)
Jest config in package.json:
"jest": {
"verbose":true,
"setupFiles": ["<rootDir>/jest.setup.js"],
"preset": "react-native",
"testMatch": [
"<rootDir>/tests/**/*.test.js?(x)",
"<rootDir>/src/**/*.test.js"
],
"transformIgnorePatterns": ["node_modules/(?!(jest-)?react-native|react-navigation|#react-native-community|#react-navigation/.*)"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}
}
It seems like NavigationEvents is wrapped in withNavigation but i don't know how to mock this behavior.
Also i cant find in react-navigation docs how to implement some testing logic, do i have to mock the library or not?
Any help would be greatly appreciated.
THANKS!
The solution i found for this problem, is adding this mock to jest.config.js file:
jest.mock('react-navigation', () => ({
NavigationEvents: 'mockNavigationEvents',
}));

Testing using Jest the implementation of Redux-persist with async-storage

I have set up redux-persist with async-storage but my jest tests are failing with an error that refers to async code. The error is cryptic and I can't find what is causing it.
> app#0.0.1 test /Desktop/app
> jest --detectOpenHandles
ts-jest[main] (WARN) Replace any occurrences of "ts-jest/dist/preprocessor.js" or "<rootDir>/node_modules/ts-jest/preprocessor.js" in the 'transform' section of your Jest config with just "ts-jest".
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at Object.get BackHandler [as BackHandler] (node_modules/react-native/Libraries/react-native/react-native-implementation.js:222:12)
at new NavigationContainer (node_modules/#react-navigation/native/dist/createAppContainer.js:190:35)
at constructClassInstance (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:2996:22)
at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5896:9)
at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6674:20)
at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10041:16)
● Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "The above error occurred in the <NavigationContainer> component:
in NavigationContainer (created by App)
in App
in PersistGate
in Provider
Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/docs/error-boundaries.html to learn more about error boundaries.".
at CustomConsole.error (node_modules/#jest/console/build/CustomConsole.js:123:10)
at logCapturedError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7959:17)
at logError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7995:9)
at update.callback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8912:9)
at callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7324:16)
at commitUpdateEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7357:11)
at commitUpdateQueue (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7347:7)
at commitLifeCycles (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8231:15)
at commitAllLifeCycles (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9531:11)
at Object.invokeGuardedCallbackImpl (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:2057:14)
PASS __tests__/App-test.tsx
✓ renders correctly (11ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.118s, estimated 5s
Ran all test suites.
npm ERR! Test failed. See above for more details.
I have tried both of the jest implementation provided by react-native-community.
https://github.com/react-native-community/async-storage/blob/master/docs/Jest-integration.md
And this SO answer but I can't figure it out.
How to test Async Storage with Jest?
/**
* App-test.txt
*/
import 'react-native'
import renderer from 'react-test-renderer'
import React from 'react'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/es/integration/react'
import mockAsyncStorage from '#react-native-community/async-storage/jest/async-storage-mock';
import App from '../App'
// Note: test renderer must be required after react-native.
import configureStore from './../app/store'
const { store, persistor } = configureStore()
jest.mock('#react-native-community/async-storage', () => mockAsyncStorage);
jest.mock('NativeModules', () => ({
UIManager: {
RCTView: () => ({
directEventTypes: {}
})
},
KeyboardObserver: {},
RNGestureHandlerModule: {
attachGestureHandler: jest.fn(),
createGestureHandler: jest.fn(),
dropGestureHandler: jest.fn(),
updateGestureHandler: jest.fn(),
State: {},
Directions: {}
}
}))
it('renders correctly', () => {
renderer.create(
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>
)
})
my jest config in package.json
"jest": {
"setupFiles": ["./__mocks__/#react-native-community/async-storage.js"],
"preset": "react-native",
"transformIgnorePatterns": [
"/node_modules/(?!native-base)/"
],
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
],
"cacheDirectory": ".jest/cache"
},
async-storage.js
import mockAsyncStorage from '#react-native-community/async-storage/jest/async-storage-mock';
jest.mock('#react-native-community/async-storage', () => mockAsyncStorage);

React-native jest test failing: TypeError: Cannot read property 'default' of undefined

I have the following code which I want to write a snapshot test.
import React from 'react';
import { AsyncStorage, View, Button, Text, StyleSheet } from 'react-native';
import t from 'tcomb-form-native';
const Login = t.struct({
email: t.String,
password: t.String,
});
const Form = t.form.Form;
class SignInScreen extends React.Component {
render() {
return (
<View style={styles.container}>
<Form ref={c => this._form = c} type={Login}/>
<Button title="Submit" onPress={this.handleSubmit}/>
<Button title="Sign Up" onPress={this.handleSignUp}/>
</View>
);
}
}
export default SignInScreen;
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
marginTop: 15,
padding: 20,
backgroundColor: '#ffffff',
}
});
I run jest in the command line, it works well test passes until I define onPress() methods.
validateCredentials = (email, password) => {
return true;
};
handleSubmit = async () => {
const value = this._form.getValue();
if(value != null) {
/*
TODO: Make a query to backend to validate credentials
*/
if (this.validateCredentials(value['email'], value['password'])) {
await AsyncStorage.setItem('userToken', 'abc');
this.props.navigation.navigate('App');
} else {
alert('Invalid email or password');
}
}
}
handleSignUp = () => {
this.props.navigation.navigate('SignUp');
}
Then it failing with error message that I don't really understand.
FAIL __tests__/SignInScreen-test.js
✕ SignInScreen snapshot test (24ms)
● SignInScreen snapshot test
TypeError: Cannot read property 'default' of undefined
9 | });
10 | const Form = t.form.Form;
> 11 | class SignInScreen extends React.Component {
| ^
12 |
13 |
14 | render() {
at new SignInScreen (screens/SignInScreen.js:11:423)
at constructClassInstance (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4810:22)
at updateClassComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6581:9)
at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7408:20)
at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10149:16)
at workLoop (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10181:28)
at renderRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10267:11)
at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11135:11)
at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11047:11)
at performSyncWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11021:7)
console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8075
The above error occurred in the <SignInScreen> component:
in SignInScreen (at SignInScreen-test.js:7)
jest config:
"jest": {
"preset": "react-native",
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
},
"transformIgnorePatterns": []
}
.babelrc
{
"presets": ["module:metro-react-native-babel-preset"]
}
My test code:
import 'react-native';
import React from 'react';
import SignInScreen from '../screens/SignInScreen';
import renderer from 'react-test-renderer';
test('SignInScreen snapshot test', () => {
const snap = renderer.create(<SignInScreen/>).toJSON();
expect(snap).toMatchSnapshot();
});
Any ideas?
Usually this error is related jest being unable to find a mock of a particular module. You need to mock tcomb as it is rendering your <Form/> component called in your SignInClass you can do so like this:
jest.mock('tcomb-form-native', () => {
const React = require('React')
const t = require.requireActual('tcomb-form-native')
// Patch the base Component class to make rendering possible.
t.form.Component.prototype.render = function render () {
return React.createElement(this.getTemplate().name, this.props)
}
return t
})