Jest testing React-native component which uses NavigationEvents trows error - react-native

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

Related

I can't render #react-navigation's NavigationContainer in tests using react native testing library

When I try to test my entire application, I'm receiving this error (full log at the end):
Screen(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
The test that is failing:
import React from "react";
import { render, waitFor } from "#testing-library/react-native";
import Routes from "./index";
describe("Routes", () => {
describe("Header Routes", () => {
it("test", async () => {
const { getByText } = render(<Routes />);
await waitFor(() => expect(getByText("Test")).toBeTruthy());
});
});
});
routes:
import React from "react";
import { NavigationContainer } from "#react-navigation/native";
import DrawerNavigators from "./drawer.routes";
const Routes = () => (
<NavigationContainer>
<DrawerNavigators /> // (routes)
</NavigationContainer>
);
export default Routes;
DrawerNavigators (routes)
import React from "react";
import { createDrawerNavigator } from "#react-navigation/drawer";
const Drawer = createDrawerNavigator();
import Test from "#pages/TestShouldNotGoToGit";
function NestingNavigators() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Test" component={Test} />
</Drawer.Navigator>
);
}
export default NestingNavigators;
and finally, the only page of the router:
import React from "react";
import { View, Text } from "react-native";
function Test() {
return (
<View>
<Text>Test</Text>
</View>
);
}
export default Test;
What should I do to render it properly?
entire log:
FAIL src/routes/Routes.func.test.tsx
Routes
Header Routes
✕ test (43 ms)
● Routes › Header Routes › test
Screen(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
at reconcileChildFibers (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5097:23)
at reconcileChildren (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7234:28)
at mountIndeterminateComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7968:5)
at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9019:16)
at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:12649:12)
at workLoopSync (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:12622:22)
at performSyncWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:12333:9)
at node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1825:24
at unstable_runWithPriority (node_modules/scheduler/cjs/scheduler.development.js:653:12)
at runWithPriority (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1775:10)
console.error
The above error occurred in the <Screen> component:
in Screen (created by NestingNavigators)
in Navigator (created by NestingNavigators)
in NestingNavigators (created by Routes)
in EnsureSingleNavigator (created by ForwardRef(BaseNavigationContainer))
in ForwardRef(BaseNavigationContainer) (created by ForwardRef(NavigationContainer))
in ThemeProvider (created by ForwardRef(NavigationContainer))
in ForwardRef(NavigationContainer) (created by Routes)
in Routes
Consider adding an error boundary to your tree to customize error handling behavior.
Visit fb.me/react-error-boundaries to learn more about error boundaries.
at logCapturedError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10141:21)
at logError (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10178:5)
at update.callback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:11288:5)
at callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:3259:12)
at commitUpdateQueue (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:3280:9)
at commitLifeCycles (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10497:11)
at commitLayoutEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:13295:7)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 2.71 s, estimated 3 s
Ran all test suites related to changed files.
Watch Usage: Press w to show m
Done in 819.28s.
First I tried without the waitFor but got the same error

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

Exception in HostFunction: <unknown> when trying to push new screen

Issue Description
Trying to push a new screen with
Navigation.push(this, {
component: {
name: 'awesome-places.AuthScreen' }})
And getting an error Exepction in HostFunction <unknown>
Error Screenshot
Steps to Reproduce / Code Snippets / Screenshots
Create RN app, install required modules (redux, react-redux, react-native-navigation, react-vector-icons) and run code on Android device.
Add relevant code, App.js and component that causes the error. I tried running Navigation.push with this.props.componentId but when I press the button there is no response
App.js
import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';
import AuthScreen from './src/screens/Auth/Auth';
import SharePlaceScreen from './src/screens/SharePlace/SharePlace';
import FindPlaceScreen from './src/screens/FindPlace/FindPlace';
import PlaceDetailScreen from './src/screens/PlaceDetail/PlaceDetail';
import configureStore from './src/store/configureStore';
const store = configureStore();
// Register Screens
Navigation.registerComponentWithRedux("awesome-places.AuthScreen", () => AuthScreen, Provider, store);
Navigation.registerComponentWithRedux("awesome-places.SharePlaceScreen", () => SharePlaceScreen, Provider, store);
Navigation.registerComponentWithRedux("awesome-places.FindPlaceScreen", () => FindPlaceScreen, Provider, store);
Navigation.registerComponent("awesome-places.PlaceDetailScreen", () => PlaceDetailScreen);
// Start an App
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
component: {
name: 'awesome-places.AuthScreen'
}
}
});
});
FindPlace.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { connect } from 'react-redux';
import { Navigation } from 'react-native-navigation';
import PlaceList from '../../components/PlaceList/PlaceList';
class FindPlaceScreen extends Component {
Navigation.push(this, {
component: {
name: 'awesome-places.AuthScreen',
}
})
}
render () {
return(
<View>
<PlaceList places={this.props.places} onItemSelected={this.itemSelectedHandler} />
</View>
);
}
}
const mapStateToProps = state => {
return {
places: state.places.places
}
}
export default connect(mapStateToProps)(FindPlaceScreen);
Environment
React Native Navigation version: 2.17.0
React Native version: 0.59.4
Platform(s) (iOS, Android, or both?): Android
Device info (Simulator/Device? OS version? Debug/Release?): Android 7.1.2, Debug
I noticed you're pushing the screen with this instead of an explicit componentId.
When calling Navigation.push, the first argument needs to be a componentId. See the docs for reference.
Also, this might not be a problem, but registerComponentWithRedux is deprecated and you should register the screen with the new api

TabBar Navigation with Redux Error

I'm currently following this article. I'm currently encountering an error in the TabBarNavigation. From what I could understand the problem could be on the connect method when I export the class. However I have wrapped this in the <Provider store={store}> <TabBarNavigation/> </Provider>.
This is the code where I'm getting the error:
import React, { Component } from 'react';
import { addNavigationHelpers } from 'react-navigation';
import { TabBar } from '../navigationConfiguration';
import { connect } from 'react-redux';
import { View } from 'react-native';
const mapStateToProps = (state) => {
return {
navigationState: state.tabBar,
}
}
class TabBarNavigation extends Component {
render(){
const { dispatch, navigationState } = this.props
return (
<TabBar
navigation={
addNavigationHelpers({
dispatch: dispatch,
state: navigationState,
})
}
/>
)
}
}
export default connect(mapStateToProps)(TabBarNavigation)
And this is the error
TypeError: undefined is not a function (evaluating 'addListener')
This error is located at:
in withCachedChildNavigation(TabView) (at TabNavigator.js:34)
in Unknown (at createNavigator.js:13)
in Navigator (at createNavigationContainer.js:226)
in NavigationContainer (at TabNavigation.js:18)
in TabBarNavigation (created by Connect(TabBarNavigation))
in Connect(TabBarNavigation) (at index.js:15)
in Provider (at index.js:14)
in SampleNavigation (at renderApplication.js:35)
in RCTView (at View.js:78)
in View (at AppContainer.js:102)
in RCTView (at View.js:78)
in View (at AppContainer.js:122)
in AppContainer (at renderApplication.js:34)
<unknown>
getChildEventSubscriber.js:60:16
getChildEventSubscriber
getChildEventSubscriber.js:59:49
<unknown>
withCachedChildNavigation.js:53:12
_updateNavigationProps
withCachedChildNavigation.js:45:38
componentWillMount
withCachedChildNavigation.js:16:34
callComponentWillMount
NativeModules.js:59:2
mountClassInstance
NativeModules.js:150:13
updateClassComponent
ReactNativeRenderer-dev.js:863:3
beginWork
ReactNativeRenderer-dev.js:1563:9
performUnitOfWork
ReactNativeRenderer-dev.js:4726:9
workLoop
ReactNativeRenderer-dev.js:4782:5
invokeGuardedCallback
react.development.js:1163:12
invokeGuardedCallback
react.development.js:1246:4
renderRoot
ReactNativeRenderer-dev.js:4828:6
performWorkOnRoot
performWork
ReactNativeRenderer-dev.js:5565:21
performSyncWork
ReactNativeRenderer-dev.js:5546:9
requestWork
ReactNativeRenderer-dev.js:5457:16
scheduleWorkImpl
ReactNativeRenderer-dev.js:5265:9
scheduleWork
ReactNativeRenderer-dev.js:5208:2
scheduleRootUpdate
ReactNativeRenderer-dev.js:5997:6
updateContainerAtExpirationTime
ReactNativeRenderer-dev.js:6038:2
updateContainer
ReactNativeRenderer-dev.js:6063:2
render
renderApplication
renderApplication.js:58:21
run
AppRegistry.js:104:10
runApplication
AppRegistry.js:196:26
__callFunction
react.development.js:684:1
<unknown>
react.development.js:377:9
__guardSafe
react.development.js:633
callFunctionReturnFlushedQueue
react.development.js:376:9
The code of the TabBar component:
import { TabNavigator } from 'react-navigation';
import TabOneNavigation from '../tabOne/views/TabOneNavigation'
import TabTwoNavigation from '../tabTwo/views/TabTwoNavigation';
const routeConfiguration = {
TabOneNavigation: { screen: TabOneNavigation },
TabTwoNavigation: { screen: TabTwoNavigation },
}
const tabBarConfiguration = {
tabBarOptions: {
activeTintColor: 'white',
inactiveTintColor: 'blue',
activeBackgroundColor: 'blue',
inactiveBackgroundColor: 'white',
}
}
Code of my Store:
import { applyMiddleware, combineReducers, createStore, compose } from 'redux';
import { createLogger } from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
// Navigation Configs
import { NavigatorTabOne } from './tabOne/navigationConfiguration';
import { NavigatorTabTwo } from './tabTwo/navigationConfiguration';
import { TabBar } from './tabBar/navigationConfiguration';
// Middleware
const loggerMiddleware = createLogger({ predicate: (getState, action) => __DEV__ });
function configureStore(initialState) {
const enhancer = compose(
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware,
),
);
return createStore(
combineReducers({
tabBar: (state, action) => TabBar.router.getStateForAction(action, state),
tabOne: (state, action) => NavigatorTabOne.router.getStateForAction(action, state),
tabTwo: (state, action) => NavigatorTabTwo.router.getStateForAction(action, state),
}),
initialState,
enhancer
);
}
export const store = configureStore({})
export const TabBar = TabNavigator(routeConfiguration, tabBarConfiguration);
How can I fix this?
The redux integration has changed.
Check following document.
https://reactnavigation.org/docs/redux-integration.html

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