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

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);

Related

jest unexpected identifier when I add setup files

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)"
]
},

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',
}));

Cannot read property 'Direction' of undefined, tests only

I just added TouchableOpacity to a component and the app is working fine, but my tests, using react-native-testing-library, fail to run:
● Test suite failed to run
TypeError: Cannot read property 'Direction' of undefined
at Object.Direction (node_modules/react-native-gesture-handler/Directions.js:3:39)
at Object.<anonymous> (node_modules/react-native-gesture-handler/GestureHandler.js:2:1)
I just removed and re-added react-native-gesture-handler with yarn, and ran pod install. Again, the app is working, but the tests fail to run.
I actually get the same error when using <Text onPress={() => onOptionPress(opt)} /> rather than TouchableOpacity.
component:
const SelectOptions = ({ field, dismissOverlay, onOptionPress }) => {
return (
<Overlay
isVisible
overlayStyle={styles.overlay}
height={"auto"}
onBackdropPress={dismissOverlay}
>
<View>
{field.options.map((opt, i) => (
<TouchableOpacity
style={styles.option}
key={i}
onPress={() => onOptionPress(opt)}
>
<Text>{opt}</Text>
</TouchableOpacity>
))}
</View>
</Overlay>
);
};
test:
describe("CardFormView", () => {
let wrapper, birthdayField;
beforeEach(() => {
wrapper = render(
<React.Fragment>
<CardFormView form={form} />
</React.Fragment>
);
birthdayField = wrapper.getByText("Add a Birthday Gift Card");
});
const message1 =
"...";
const message2 =
"...";
it("shows the options for a birthday card when clicked", () => {
fireEvent.press(birthdayField);
expect(wrapper.getByText(message1)).toBeDefined();
});
it("sets an option when clicked", () => {
fireEvent.press(birthdayField);
const firstOption = wrapper.getByText(message1);
fireEvent.press(firstOption);
expect(wrapper.queryByText(message2)).toBeNull();
expect(wrapper.getByText(message1)).toBeDefined();
});
});
This is because you are not mocking the react-navigation-gesture-handler
To use mock of react-navigation-gesture-handler you should add jestSetup.js from node_modules in jest.config.json or jest.config.js
setupFiles: [
"./node_modules/react-native-gesture-handler/jestSetup.js"
]
I found a reference from the following link and It's working for me.
https://github.com/software-mansion/react-native-gesture-handler/issues/344#issuecomment-489547513
For me just adding the setupFiles didn't work. I added setupFiles and transformIgnorePatterns at "jest" in package.json
Here the code to make the gestureHandler work, but I tested it with AsyncStorage and the storage stopped work. If you aren't using AsyncStorage I presume this code will work very well!
"setupFiles": [
"./node_modules/react-native-gesture-handler/jestSetup.js"
],
"transformIgnorePatterns": [
"/node_modules/(?!native-base)/"
]
My reference:
https://github.com/software-mansion/react-native-gesture-handler/issues/344
Updating package.json and reinstalling npm package worked for me.
"jest": {
"preset": "react-native",
"transformIgnorePatterns": ["node_modules/(?!(jest-)?react-native|#?react-navigation)"],
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
}
This is happening because you have to mock the NativeModules module from react-native. It can happen with several modules but it was happening to me specifically with the ImagePicker, Linking and #react-navigation/native. This is what I did to mock the native modules.
/src/testSetup.ts
import {NativeModules} from 'react-native';
NativeModules.RNGestureHandlerModule= {
attachGestureHandler: jest.fn(),
createGestureHandler: jest.fn(),
dropGestureHandler: jest.fn(),
updateGestureHandler: jest.fn(),
State: {},
Directions: {},
},
NativeModules.ImagePickerManager = {
showImagePicker: jest.fn(),
}
NativeModules.Linking = {
canOpenUrl: jest.fn().mockResolvedValue(true),
openUrl: jest.fn().mockResolvedValue(true)
}
NativeModules.Platform = {
OS: 'iOS'
}
jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
jest.mock('react-native/Libraries/Animated/src/animations/TimingAnimation');
const mockNavigation = () => {
const mockedNavigate = jest.fn();
const mockedAddListener = jest.fn();
jest.mock('#react-navigation/native', () => ({ // #ts-ignore
...(jest.requireActual('#react-navigation/native')),
useNavigation: () => ({
navigate: mockedNavigate,
addListener: mockedAddListener
})
}));
return {mockedNavigate, mockedAddListener}
}
in your tests
import { fireEvent, act, render } = '#testing-library/react-native'
const {mockedNavigate, mockedAddListener} = mockNavigation()
test('Should navigate', () => {
const { queryByText } = render(<Component />)
fireEvent.press(getByText('View Page Button'))
expect(mockedNavigate).toHaveBeenCalledWith('Your Page Name')
expect(mockedAddListener).toHaveBeenCalled()
})
In my case, I was using react-native-cli when encountered this problem. I removed it and installed #react-native-community/cli instead. It fixed everything!

undefined is not a function (evaluating 'decorator(target, property, desc)')

I want to integrate mobx and mobx-persist with react-navigation.
I read these articles:
[1] https://hackernoon.com/react-navigation-with-mobx-2064fcdaa25b
[2] https://blog.callstack.io/write-react-native-apps-in-2017-style-with-mobx-e2dffc209fcb
[3] https://github.com/react-navigation/react-navigation/blob/8e8d3d562c9e80616f145f97ffb02dcf2048e67e/docs/guides/Mobx-Integration.md
[4] MobX + React Native : way to inject stores
[5] MobX - Why should I use `observer` when I could use `inject` when injecting data into a React component
[6] Injecting Store in React component results in Error
But I still got this error:
undefined is not a function (evaluating 'decorator(target, property, desc)')
This is my App.js render:
render() {
const hydrate = create({
storage: AsyncStorage
});
hydrate('playerStore', stores.PlayerStore);
hydrate('settingStore', stores.SettingStore);
// .then(
// // () => console.warn('some hydrated')
// );
return <Provider {...stores} >
<AppWithNavigationState />
</Provider>;
}
This is my routeStore.js:
import {observable} from "mobx";
import {action} from "mobx-react/native";
import AppDrawer from "../appDrawer";
import {autobind} from 'core-decorators';
export default class RouteStore {
#observable.ref navigationState = {
index: 0,
routes: [
{ key: "R1", routeName: "ContentStack" },
],
};
#action
dispatchNavigation(action, stackState = true) {
const previousNavState = stackState ? this.navigationState : null;
return this.navigationState = AppDrawer.router.getStateForAction(action, previousNavState);
}
}
This is my appWithNavigationState.js:
import React from 'react';
import {observer, inject} from "mobx-react/native";
import {addNavigationHelpers} from "react-navigation";
import AppDrawer from "../appDrawer";
#inject(stores => ({ routeStore: stores.RouteStore }))
#observer
export default class AppWithNavigationState extends React.Component {
render() {
return (
<AppDrawer navigation={addNavigationHelpers({
dispatch: this.props.routeStore.dispatchNavigation,
state: this.props.routeStore.navigationState,
})} />
);
}
}
I also use decorator package as below:
npm install babel-plugin-transform-decorators-legacy --save-dev
and this setting in babelrc file:
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}
Can you help me to fix this?
By default, the React Native Babel transform configuration does not include support for ES7 decorators.
You can add them by first installing the decorator package:
npm install babel-plugin-transform-decorators-legacy --save-dev
And then adding the plugin in your .babelrc file in the project root:
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}