React native elements with jest not working - react-native

I'm using #testing-library/react-native but when I try to test a component that has any rneui: 4.0.0-rc-6 components I get several errors:
The first now was
Details:
/Users/ep/myProject/node_modules/#rneui/themed/dist/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { AirbnbRatingDefault as AirbnbRating, } from './AirbnbRating';
I was able to solve this by adding #rneui to transformIgnorePatterns inside package.json
but now Im getting
ReferenceError: getCacheKeyFunction is not defined
at _default (node_modules/#jest/create-cache-key-function/build/index.js:76:3)
at Object.<anonymous> (node_modules/jest-expo/src/preset/assetFileTransformer.js:5:16)
Any ideas on how to solve this?
(my component is using Icon and Input from rneui)

I was able to solve these by mocking the components
const MockInput = () => (<View />)
jest.mock('#rneui/themed', () => ({
// AirbnbRating: jest.fn()
Input: jest.fn(() => <MockInput />),
Icon: jest.fn(() => <></>)
}))

Related

RNEncryptedStorage is undefined while running testing-library/react-native with Jest

I am trying to setup my react-native test environment with react-native-testing-library and Jest. My react-native application uses react-native-encrypted-storage. When I run my first sample test (code below) it fails saying RNEcryptedStorage is undefined.
import React from "react";
import "react-native";
// Note: test renderer must be required after react-native.
import renderer from "react-test-renderer";
import App from "../App";
it("renders correctly", () => {
console.log("Rendering");
renderer.create(<App />);
});
Full error:
RNEncryptedStorage is undefined
at Object. (node_modules/react-native-encrypted-storage/lib/commonjs/EncryptedStorage.ts:7:9)
at Object. (node_modules/react-native-encrypted-storage/lib/commonjs/index.ts:1:1)
This is first time I am setting up my test environment so not sure where do I start for resolving this issue.
The above will work, but if you mock other aspects of react-native it could create a problem with the other mocks. If you want to mock RNEncryptedStorage on its own, you could try a slight variation of the above solution:
__mocks__/react-native-encrypted-storage/index.js
const RNEncryptedStorage = {
setItem: jest.fn(() => Promise.resolve()),
getItem: jest.fn(() => Promise.resolve('{ "foo": 1 }')),
removeItem: jest.fn(() => Promise.resolve()),
clear: jest.fn(() => Promise.resolve()),
};
export default RNEncryptedStorage;
You can mock the RNEncryptedStorage native module during your tests by adding a react-native mock to your __mocks__ folder.
// tests/__mocks__/react-native.js
module.exports = {
NativeModules: {
RNEncryptedStorage: {
setItem: jest.fn(() => Promise.resolve()),
getItem: jest.fn(() => Promise.resolve('{ "foo": 1 }')),
removeItem: jest.fn(() => Promise.resolve()),
clear: jest.fn(() => Promise.resolve())
}
}
}

UI Kitten functional component - Props undefined

I'm developing a react native mobile app using UI Kitten. I'm still fairly new to both react native and UI kitten, so I am using the just the plain Javascript template VS the Typescript template.
I have a functional component screen as shown below. This screen is working just fine. Today I started REDUX implementation.
const RequestScreen = ({ navigation }) => {
// code removed for brevity
}
Within this screen I use the useEffect hook to fetch data from my API
useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
getServices();
});
return () => {
unsubscribe;
};
}, [navigation]);
const getServices = async () => {
setLoading(true);
// helper function to call API
await getAllServices().then((response) => {
if (response !== undefined) {
const services = response.service;
setServices(services);
setLoading(false);
}
});
// update redux state
props.getAllServices(services);
};
// code removed for brevity
const mapStateToProps = (state) => state;
const mapDispatchToProps = (dispatch) => ({
getServices: (services) =>
dispatch({
type: Types.GET_SERVICES,
payload: { services },
}),
});
const connectComponent = connect(mapStateToProps, mapDispatchToProps);
export default connectComponent(RequestScreen);
On this line of code:
props.getAllServices(services);
I keep getting this error:
[Unhandled promise rejection: TypeError: undefined is not an object
(evaluating 'props.getAllServices')] at
node_modules\regenerator-runtime\runtime.js:63:36 in tryCatch at
node_modules\regenerator-runtime\runtime.js:293:29 in invoke
Anytime I try to use "props" in code here. I run into errors. How do I get the props on this screen?
I tried changing the screen, as shown below, but that does not work either!
const RequestScreen = ({ navigation, props }) => {
// code removed
}
I was able to get the props object after changing the screen component as shown below.
const RequestScreen = ({ navigation, ...props }) => {}

Testing app containing AppLoading component

I am new to the world of unit testing and I have just began to write tests for my React Native (Expo) app. After doing research I have finally landed in using Jest and React Native Testing Library.
Consider the following that uses the AppLoading component.
const App: React.FC = () => {
const [resourcesHasLoaded, setResourcesHasLoaded] = useState<boolean>(false);
const cacheResources = useCallback(async (): Promise<any> => {
const images = [require('./assets/icon.png')];
const cacheImages = images.map((image) => {
return Asset.fromModule(image).downloadAsync();
});
return Promise.all([cacheImages]);
}, []);
if (resourcesHasLoaded) {
return <Text>Hello world</Text>;
}
return (
<AppLoading
startAsync={cacheResources}
onError={console.warn}
onFinish={() => setResourcesHasLoaded(true)}
/>
);
};
When running my test, that looks like this:
describe('App.tsx', () => {
it('should be able to render', async () => {
render(<App />);
});
});
I end up with the following error (although, test passes):
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
So, I wrapped my `render` in with `act` the following way:
act(() => {
render(<App />);
});
... which resulted in the same error.
If I however wrap the onFinish-callback in my component the following way the test passes without warnings.
onFinish={() => act(() => setResourcesHasLoaded(true))}
But do I really want to pollute my React Component with test-specific functions? I saw no example of this, so I can only assume that this is bad practice.
Any suggestions here?
Update
I got the suggestion to use waitFor after my render by #Estus Flask in my comments. That did the trick... the test now passes.
https://callstack.github.io/react-native-testing-library/docs/api/#waitfor
describe('App.tsx', () => {
it('should be able to render', async () => {
const { findByText } = render(<MyApp />);
await waitFor(() => findByText('Hello world'));
});
});

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!

Is there a proper way to use useNavigation() hook with useEffect() dependencies?

I'm trying to interact with react-navigation using useNavigation() hook in response to a callback I'm registering in useEffect(). The linter is warning me that useEffect() has a missing dependency. If I add the navigation hook as a dependency, the effect continuously runs. I'm trying to avoid this and wondering if there is a correct way other than ignoring the linter error.
Providing no dependency array results in the same behavior where the effect continuously fires.
This may be an underlying issue with how the useNavigation() hook from react-navigation-hooks package works.
function MyComponent() {
const navigation = useNavigation();
useEffect(() => {
navigation.navigate('Home');
}, []);
}
Results in:
React Hook useEffect has a missing dependency: 'navigation'. Either include it or remove the dependency array.
Just an opinionated guess: It's more a question regarding your "architecture".
For example: Wouldn't it make more sense for the custom useNavigation hook to return a function that can be called by the consumer of the hook instead of an object with all it's functionality?
Here is an example:
const useNavigation = () => {
const [routes, setRoutes] = useState(null);
...
const navigate = (destination: string) => {
console.log("navigated to ", destination);
};
return { navigate, routes };
};
function App() {
const { navigate } = useNavigation();
return (
<div className="App">
<h1>Parent</h1>
<button onClick={() => navigate("Home")}>Navigate me!</button>
</div>
);
}
Working Codesandbox: https://codesandbox.io/s/usenavigation-95kql
If you nevertheless want to keep this "architecture", you could use a useRef hook like so:
const navigation = useRef(useNavigation());
useEffect(() => {
navigation.current.navigate("Home");
}, []);
I believe the error message is clear, you are missing the useEffect dependency:
function MyComponent() {
const navigation = useNavigation();
useEffect(() => {
if (!navigation) return; // <-- this will avoid any undefined or null calls
navigation.navigate('Home');
}, [navigation]); // <-- this dependency
}