Unit Test Not Reach 100% Coverage IF/ELSE Not Taken in Imports - react-native

The Unit test with jest / Enzyme or React-Native-Testing-Library never reaches 100% of code coverage for problems with the import, some import show warning IEEIEIE if/else not taken
These modules are in node_modules (and node_modules is include in the coveragePathIgnorePatterns and exclude from collectCoverageFrom) or custom modules that do not even have if / else statements
import React from 'react';
import { cleanup, render } from 'react-native-testing-library';
import ItemListaComprobante from './ItemListaComprobante';
describe('test container ConfiguracionPin', () => {
let wrapper;
const props = {
label: 'Test',
value: 'Test',
};
afterEach(() => cleanup);
beforeEach(() => {
wrapper = render(<ItemListaComprobante {...props} />);
});
test('test container ConfiguracionPin render properly ', () => {
expect(wrapper).toMatchSnapshot();
});
});
import React, { memo } from 'react';
import { Text } from 'react-native'; // (without IEEI)
import PropTypes from 'prop-types';
import { // IEEI
Left,
Right,
CardItem,
} from 'native-base';
const propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
};
const ItemListaComprobante = ({ label, value }) => (
<CardItem
bordered
borderedWhite
>
<Left>
<Text>{label}</Text>
</Left>
<Right>
<Text
bold
numberOfLines={1}
ellipsizeMode="tail"
>
{value}
</Text>
</Right>
</CardItem>
);
ItemListaComprobante.propTypes = propTypes;
export default memo(ItemListaComprobante);
My settings of jest
module.exports = {
verbose: true,
preset: 'react-native',
testEnvironment: 'jsdom',
transform: { '^.+\\.js$': '<rootDir>/node_modules/react-native/jest/preprocessor.js' },
setupFiles: ['<rootDir>/jest.setup.js'],
transformIgnorePatterns: ['/node_modules/*.js'],
coveragePathIgnorePatterns: [
'<rootDir>/index.js',
'<rootDir>/App.js',
'<rootDir>/commitlint.config.js',
'/node_modules/',
'jest.setup.js',
'ReactotronConfig.js',
'LogUtils.js',
'jest.config.js',
'rn-cli.config.js',
'transformer.js',
'super-wallet/coverage/lcov-report',
'Str.js',
],
setupFilesAfterEnv: [
'<rootDir>/__mocks__/react-native-camera.js',
'<rootDir>/__mocks__/react-native-fetch-blob.js',
'<rootDir>/__mocks__/react-native-firebase.js',
'<rootDir>/__mocks__/react-navigation.js',
'<rootDir>/__mocks__/react-native-reactotron.js',
'<rootDir>/__mocks__/react-native-user-agent.js',
'<rootDir>/__mocks__/osiris.js',
'<rootDir>/__mocks__/react-native-check-app-install.js',
'<rootDir>/__mocks__/react-native-image-crop-picker.js',
'<rootDir>/__mocks__/react-native-app-link.js',
],
collectCoverageFrom: ['**/*.{js,jsx}', '!**/node_modules/**', '!**/vendor/**'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: -10,
},
},
};
My settings of babelrc
{
"presets": ["module:metro-react-native-babel-preset"]
}
I would appreciate very much if somebody can help me, I have reviewed documentation of jest / istanbul and enzyme and I have not seen anything, even here I have also searched in case someone has had the same problem as me
This is error in istanbul coverage
Istanbul Report Printscreen error

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 test bottom tab bar using react native test library

I am trying to do testing for my react bottom tab bar component while testing it I am getting below error.
I followed all the solutions available in this Link no luck for me.
https://github.com/react-navigation/react-navigation/issues/8669
/Users/apple/Documents/MM/myproject/node_modules/#react-navigation/elements/lib/commonjs/assets/back-icon.png:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){�PNG
My testing component
Tabbar.test.js
import React from "react";
import { render, fireEvent } from "#testing-library/react-native";
import Tabbar from "../Tabbar";
it("Tab tests", () => {
const addItemButton = render(<Tabbar />).toJSON;
}
My Tab bar component
Tab.js file
import React from "react";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import { Image } from "react-native";
const Tab = createBottomTabNavigator();
const Tabbar = ({ tabData }) => {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
tabBarActiveTintColor: "00000",
tabBarInactiveTintColor: "FFFF",
})}
>
{tabsInfo.map((element) => {
return (
<Tab.Screen
key={element.idx}
name={element.tabName}
component={element.component}
/>
);
})}
</Tab.Navigator>
);
};
export default Tabbar;
This is my jest config code
jest.config.js
module.exports = {
preset: "react-native",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
setupFilesAfterEnv: ["#testing-library/jest-native/extend-expect"],
transformIgnorePatterns: [
"node_modules/(?!(#react-native|react-native|react-native-vector-icons)/)",
],
};
Update transformIgnorePatterns in the jest config file.
transformIgnorePatterns: [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|#react-native-community|rollbar-react-native|#fortawesome|#react-native|#react-navigation)",
],
Reference link https://github.com/react-navigation/react-navigation/issues/8669

Flatlist undefined is not an object React-native

I am building a simple React-native app with Expo for rating Github repositories and ran into a nasty issue. When I am trying to render a list of the repositories with Flatlist it throws me the following error: undefined is not an object (evaluating 'repository.fullName'); although my code is pretty much identical to the one in React-native docs. Here is the RepositoryList.jsx where the Flatlist is being rendered:
import React from 'react';
import { FlatList, View, StyleSheet } from 'react-native';
import RepositoryItem from './RepositoryItem'
const styles = StyleSheet.create({
separator: {
height: 10,
},
});
const repositories = [
{
id: 'rails.rails',
fullName: 'rails/rails',
description: 'Ruby on Rails',
language: 'Ruby',
forksCount: 18349,
stargazersCount: 45377,
ratingAverage: 100,
reviewCount: 2,
ownerAvatarUrl: 'https://avatars1.githubusercontent.com/u/4223?v=4',
},
{
id: 'reduxjs.redux',
fullName: 'reduxjs/redux',
description: 'Predictable state container for JavaScript apps',
language: 'TypeScript',
forksCount: 13902,
stargazersCount: 52869,
ratingAverage: 0,
reviewCount: 0,
ownerAvatarUrl: 'https://avatars3.githubusercontent.com/u/13142323?v=4',
}
];
const ItemSeparator = () => <View style={styles.separator} />;
const RepositoryList = () => {
return (
<FlatList
data={repositories}
ItemSeparatorComponent={ItemSeparator}
renderItem={({repository}) => <RepositoryItem repository={repository}/> }
/>
);
};
export default RepositoryList
and RepositoryItem.jsx which should be rendered within the Flatlist:
import React from 'react'
import {View, Text, StyleSheet} from 'react-native'
const RepositoryItem = ({repository}) => {
return(
<View style={styles.item}>
<Text>Full name:{repository.fullName}</Text>
<Text>Description:{repository.description}</Text>
<Text>Language:{repository.language}</Text>
<Text>Stars:{repository.stargazersCount}</Text>
<Text>Forks:{repository.forksCount}</Text>
<Text>Reviews:{repository.reviewCount}</Text>
<Text>Rating:{repository.ratingAverage}</Text>
</View>
)
}
styles = StyleSheet.create({
item: {
marginHorizontal: 16,
backgroundColor: 'darkorange'
},
});
export default RepositoryItem
After doing my research I found that a lot of people have run into this issue too, and apparently it persists since 0.59 (my React-native is on 0.62, Windows). Apparently the error is being cause by a babel module '#babel/plugin-proposal-class-properties' and the solution would be deleting this module from .babelrc, according to this Github thread https://github.com/facebook/react-native/issues/24421. The problem is that my babel.config.js is extremely simple, and I don't see how I can exclude this module from being required for babel to work. My babel.config.js:
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
};
};
Perhaps there is a way to exclude it through tweaking babel in node_modules, but this solution seems unlikely. Any help or suggestions regarding this issue will be greatly appreciated!
I think your problem consists in destructuring repository in your renderItem method of the FlatList.
You cannot just destructure whatever you want, you have to destructure item from the Flatlist.
Try this way:
const RepositoryList = () => {
return (
<FlatList
data={repositories}
ItemSeparatorComponent={ItemSeparator}
renderItem={({ item }) => <RepositoryItem repository={item}/> }
/>
);
};
Or, if you really want to
const RepositoryList = () => {
return (
<FlatList
data={repositories}
ItemSeparatorComponent={ItemSeparator}
renderItem={({ item: repository }) => <RepositoryItem repository={repository}/> }
/>
);
};

How can I pass the navigator prop from react-native-navigation v2 to my splash screen via Navigation.setRoot

I am trying to migrate from react-native-navigation v1 to react-native-navigation v2. I am struggling to move from
Navigation.startSingleScreenApp
to
Navigation.setRoot
When I switch from Navigation.startSingleScreenApp (v1) to Navigation.setRoot (v2), I no longer have the navigator prop that I was relying on to navigate around the application.
I have copy and pasted all relevant code below
RegisterScreens
import { Navigation } from 'react-native-navigation';
import SplashScreenScreen from './components/SplashScreen';
import { Provider } from 'react-redux';
import React from "react";
import SCREEN from './screenNames';
export default function registerScreens(store) {
Navigation.registerComponent(
SCREEN.SPLASH_SCREEN,
() => props => (<Provider store={store}><SplashScreenScreen {...props} /></Provider>), () => SplashScreenScreen);
App
import { Platform } from 'react-native';
import { Navigation } from 'react-native-navigation';
import registerScreens from './registerScreens';
import { Colors, Fonts } from './themes';
import { store } from './configureStore';
import NavigationListener from './NavigationEventListener';
import configureNotification from './configureNotification';
import SCREEN from './screenNames';
import Reactotron from 'reactotron-react-native';
const navBarTranslucent = Platform.OS === 'ios';
configureNotification();
registerScreens(store);
new NavigationListener(store);
const STARTING_SCREEN = SCREEN.SPLASH_SCREEN;
Navigation.events().registerAppLaunchedListener(() => {
Reactotron.log('5');
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
id: STARTING_SCREEN,
name: STARTING_SCREEN
}
}],
}
},
layout: {
orientation: 'portrait',
},
});
});
SplashScreen
import React from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { PersistGate } from 'redux-persist/es/integration/react';
import { navigateToFirstScreen } from '../redux/splash';
import { Colors, Fonts, Metrics } from '../themes';
import { persistor } from '../configureStore';
export class SplashScreen extends React.Component {
navigateTo = (screen) =>
this.props.navigator.push({
screen,
overrideBackPress: true,
backButtonHidden: true,
animated: false,
navigatorStyle: {
disabledBackGesture: true,
},
});
render() {
const { dispatchNavigateToFirstScreen } = this.props;
return (
<PersistGate
persistor={persistor}
onBeforeLift={() => setTimeout(() => dispatchNavigateToFirstScreen(this.navigateTo), 2000)}><View style={styles.bodyContainer}
>
<Text>Jono</Text>
</View>
</PersistGate>
);
}
}
const styles = StyleSheet.create({
bodyContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: Colors.splashScreen,
},
appTitleText: {
fontSize: Fonts.size.splashScreenTitle,
fontFamily: Fonts.type.extraBold,
lineHeight: Metrics.lineHeight.appTitle,
textAlign: 'center',
color: Colors.textLightColor,
},
});
SplashScreen.propTypes = {
navigator: PropTypes.shape({
push: PropTypes.func.isRequired,
}).isRequired,
dispatchNavigateToFirstScreen: PropTypes.func.isRequired,
};
const mapDispatchToProps = (dispatch) => {
return {
dispatchNavigateToFirstScreen: (navigateTo) =>
dispatch(navigateToFirstScreen(navigateTo)),
};
};
export default connect(null, mapDispatchToProps)(SplashScreen);
I spent multiple hours trying to solve this problem so I am going to post my conclusion as an answer.
this.props.navigator is not used anymore in 2.x.
You need to use Navigation
This dude had the same problem and reached the same conclusion: https://github.com/wix/react-native-navigation/issues/3795

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