When I am trying to use the dispatch function recieved with the useContext hook I cannot get the change the content of the data inside the context. It looks like as if the call wasn't even made, when I try to log something inside the conext's reducer it doesn't react. When I try to call it from other components, it works just fine.
Sorry if it's not clean enough, I'm not too used to ask around here, if there's anything else to clarify please tell me, and I'll add the necessary info, I just don't know at the moment what could help.
import { QueryClient, QueryClientProvider } from "react-query";
import LoginPage from "./src/pages/LoginPage";
import { UserDataContext, UserDataProvider } from "./src/contexts/UserData";
import { useState } from "react";
import AsyncStorage from "#react-native-async-storage/async-storage";
import { useContext } from "react";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { useCallback } from "react";
import { UserData } from "./src/interfaces";
SplashScreen.preventAutoHideAsync();
const queryClient = new QueryClient();
export default function App() {
const [appReady, setAppReady] = useState<boolean>(false);
const { loggedInUser, dispatch } = useContext(UserDataContext);
useEffect(() => {
async function prepare() {
AsyncStorage.getItem("userData")
.then((result) => {
if (result !== null) {
console.log(loggedInUser);
const resultUser: UserData = JSON.parse(result);
dispatch({
type: "SET_LOGGED_IN_USER",
payload: resultUser,
});
new Promise((resolve) => setTimeout(resolve, 2000));
}
})
.catch((e) => console.log(e))
.finally(() => setAppReady(true));
}
if (!appReady) {
prepare();
}
}, []);
const onLayoutRootView = useCallback(async () => {
if (appReady) {
await SplashScreen.hideAsync();
}
}, [appReady]);
if (!appReady) {
return null;
}
return (
<>
<UserDataProvider>
<QueryClientProvider client={queryClient}>
<LoginPage onLayout={onLayoutRootView} />
</QueryClientProvider>
</UserDataProvider>
</>
);
}
I'm thinking I use the context hook too early on, when I check the type of the dispatch function here it says it's [Function dispatch], and where it works it's [Function bound dispatchReducerAction].
I think the problem might come from me trying to call useContext before the contextprovider could render, but even when I put the block with using the dispatch action in the onLayoutRootView part, it didn't work.
I'm trying to write a test case for testing URL in my react native app this is my mock
import { Linking } from "react-native";
jest.mock('react-native/Libraries/Linking/Linking', () => {
return {
openURL: jest.fn()
}
})
Linking.openURL.mockImplementation(() => true)
and this is my test
test('open google url',async ()=>{
expect(Linking.openURL()).toHaveBeenCalled('https://www.google.com/')
})
but I get this error what should I do?
Name the mock function in a constant and then test if that function has been called. Here's how you would set up:
import * as ReactNative from "react-native";
const mockOpenURL = jest.fn();
jest.spyOn(ReactNative, 'Linking').mockImplementation(() => {
return {
openURL: mockOpenURL,
}
});
and then you can test this way (my example uses react-testing-library, but you can use whatever). Note you should use toHaveBeenCalledWith(...) instead of toHaveBeenCalled(...)
test('open google url', async () => {
// I assume you're rendering the screen here and pressing the button in your test
// example code below
const { getByTestId } = render(<ScreenToTest />);
await act(async () => {
await fireEvent.press(getByTestId('TestButton'));
});
expect(mockOpenURL.toHaveBeenCalledWith('https://www.google.com/'));
});
If I understoof your question then you can use react-native-webview.
import WebView from 'react-native-webview';
export const WebView: React.FC<Props> = ({route}) => {
const {url} = route.params;
<WebView
source={{uri: url}}
/>
);
};
This is how I use my webview screen for any url I need to open (like terms and conditions, etc...)
I have a custom react hook 'useSample' which uses useNavigation and useNavigationParam
import { useContext } from 'react'
import { useNavigation, useNavigationParam } from 'react-navigation-hooks'
import sampleContext from '../sampleContext'
import LoadingStateContext from '../LoadingState/Context'
const useSample = () => {
const sample = useContext(sampleContext)
const loading = useContext(LoadingStateContext)
const navigation = useNavigation()
const Mode = !!useNavigationParam('Mode')
const getSample = () => {
if (Mode) {
return sample.selectors.getSample(SAMPLE_ID)
}
const id = useNavigationParam('sample')
sample.selectors.getSample(id)
navigation.navigate(SAMPLE_MODE_ROUTE, { ...navigation.state.params}) // using navigation hook here
}
return { getSample }
}
export default useSample
I need to write unit tests for the above hook using jest and I tried the following
import { renderHook } from '#testing-library/react-hooks'
import sampleContext from '../../sampleContext'
import useSample from '../useSample'
describe('useSample', () => {
it('return sample data', () => {
const getSample = jest.fn()
const sampleContextValue = ({
selectors: {
getSample
}
})
const wrapper = ({ children }) => (
<sampleContext.Provider value={sampleContextValue}>
{children}
</sampleContext.Provider>
)
renderHook(() => useSample(), { wrapper })
})
})
I got the error
'react-navigation hooks require a navigation context but it couldn't be found. Make sure you didn't forget to create and render the react-navigation app container. If you need to access an optional navigation object, you can useContext(NavigationContext), which may return'
Any help would be appreciated!
versions I am using
"react-navigation-hooks": "^1.1.0"
"#testing-library/react-hooks":"^3.4.1"
"react": "^16.11.0"
You have to mock the react-navigation-hooks module.
In your test:
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
jest.mock('react-navigation-hooks');
And it's up to you to add a custom implementation to the mock. If you want to do that you can check how to mock functions on jest documentation.
for me, soved it by usingenter code here useRoute():
For functional component:
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '#react-navigation/native';
function MyBackButton() {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}
For class component:
class MyText extends React.Component {
render() {
// Get it from props
const { route } = this.props;
}
}
// Wrap and export
export default function(props) {
const route = useRoute();
return <MyText {...props} route={route} />;
}
I am developing a code and always need the user to enter the application to check if there is an update, if there is to send the user to an information screen. But for some reason when I use navigation.navigate ('update') it doesn't work, but console.log ("oi"); above it works. What happens is normal is that last useEffect() executes the navigation.navigate ('Menu'); In the console does not show any kind of error.
Code:
useEffect(() => {
async function verifyVersion() {
await api.post('/version', {
version: 'v1.0'
}).then((response)=>{
console.log("oi");
navigation.navigate('update');
});
}
verifyVersion();
}, []);
useEffect(() => {
async function autoLogon() {
if(await AsyncStorage.getItem("Authorization") != null){
await api.post('/checkToken', null, {
headers: { 'Authorization': 'EST ' + await AsyncStorage.getItem("Authorization") }
}).then((res)=>{
navigation.navigate('Menu');
}).catch(function (error){
if(error.response.data.showIn == "text"){
setShowInfo(true);
if(error.response.data.level == 3){
setColorInfo(false);
}else{
setColorInfo(true);
}
setInfoText(error.response.data.error);
}else{
setshowBox(true);
if(error.response.data.level == 3){
setcolorBox(false);
}else{
setcolorBox(true);
}
setboxText(error.response.data.error);
}
});
}
}
autoLogon();
}, []);
Routes:
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import Login from './pages/Login';
import read from './pages/read';
import Menu from './pages/Menu';
import Resultado from './pages/Resultado';
import NoConnection from './pages/NoConnection';
import update from './pages/update';
const Routes = createAppContainer(
createSwitchNavigator({
Login,
Menu,
read,
Resultado,
NoConnection,
update
})
);
export default Routes;
Write the navigate function call in setTimeOut for 500ms. it works
fine for me
useEffect(() => {
....
setTimeOut(() => navigation.navigate('Dashboard'), 500);
}, []);
In react-navigation, screen mounting works differently from react component mounting. You need to use a focus listener like this:
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
if (!someCondition) navigation.navigate('someScreen');
});
return unsubscribe;
}, [navigation]);
More on the topic can be found here and here
Some of the code I am trying to test detects the platform, using, e.g.:
import { Platform } from 'react-native';
...
if (Platform.OS === 'android') {
...
} else {
...
}
Is there a sensible way to mock this with Jest and/or something else, so I can test both branches in one test run?
Or is the smart way to decouple it and put the platform into, e.g., a context variable? Although it always feels restructuring code to make it easier to test is something of a cheat.
This worked for me (Jest 21.2.1, Enzyme 3.2.0):
jest.mock('Platform', () => {
const Platform = require.requireActual('Platform');
Platform.OS = 'android';
return Platform;
});
Put it either at the top of your test, or in a beforeAll for example.
For everyone looking for this, what it helped me was the following:
jest.mock('react-native/Libraries/Utilities/Platform', () => ({
OS: 'android', // or 'ios'
select: () => null
}));
The way that I achieved mocking setting the platform was just set it directly in the tests:
it('should only run for Android', () => {
Platform.OS = 'android'; // or 'ios'
// For my use case this module was failing on iOS
NativeModules.MyAndroidOnlyModule = {
fetch: jest.fn(
(url, event) => Promise.resolve(JSON.stringify(event.body))
),
};
return myParentFunction().then(() => {
expect(NativeModules.MyAndroidOnlyModule.fetch.mock.calls.length).toBe(1);
expect(fetch.mock.calls.length).toBe(0);
});
});
This would setup the platform to only run on Android during tests to make sure that my function was calling only specific functions. My function that was wrapped in platform dependent compilation looked like:
export default function myParentFunction() {
if (Platform.OS === 'ios') {
return fetch();
}
return NativeModules.MyAndroidOnlyModule.fetch();
}
I would suggest just creating two different tests one with the platform set to iOS and the other to Android since ideally a function should only have one responsibility. However, I'm sure you can use this to run the first test, dynamically set the platform and run test number two all in one function.
I implemented a small mock that allows you to change Platform during tests in the same test file.
Add this to your jest setup file
jest.mock('react-native/Libraries/Utilities/Platform', () => {
let platform = {
OS: 'ios',
}
const select = jest.fn().mockImplementation((obj) => {
const value = obj[platform.OS]
return !value ? obj.default : value
})
platform.select = select
return platform
});
Then you can easily change Platform in your test. If you are using Platform.select it will also work as expected!
import { Platform } from 'react-native'
describe('When Android', () => {
it('should ...', () => {
Platform.OS = 'android'
...
})
})
describe('When iOS', () => {
it('should ...', () => {
Platform.OS = 'ios'
...
})
})
React Native 0.61 update
Though the accepted solution works for versions of React Native 0.60 and below, React Native 0.61 has dropped Haste support and this gives an error.
I was able to mock platform detection following the implementation described in this blog post.
Practically, according to the React team, we now have to mock the react-native interface. So, you can create a react-native.js file inside the tests/__mocks__ folder and add this code to mock Platform:
import * as ReactNative from "react-native";
export const Platform = {
...ReactNative.Platform,
OS: "ios",
Version: 123,
isTesting: true,
select: objs => objs[Platform.OS]
};
export default Object.setPrototypeOf(
{
Platform
},
ReactNative
);
With this implementation, we can now simply overwrite the OS before running the test like:
Platform.OS = 'android'
Since the other answers will not work if you want to mock different OSs in the same test suite and in one test run, here's another way. Instead of using Platform.OS directly in your code, define a helper function somewhere and use that to get references to the OS in your components:
in 'helpers.js':
export function getOS() {
return Platform.OS;
}
in your component:
import * as helpers from './helpers';
render() {
if (helpers.getOS() === 'android') {// do something}
}
This function can then be mocked it in your tests, e.g.
import * as helpers from './helpers';
// ...
it('does something on Android', () => {
jest.spyOn(helpers, 'getOS').mockImplementation(() => 'android');
// ...
}
it('does something else on iOS', () => {
jest.spyOn(helpers, 'getOS').mockImplementation(() => 'ios');
// ...
}
Credit for the idea goes to this GitHub issue comment.
This works for me...
jest.mock('react-native/Libraries/Utilities/Platform', () => {
const Platform = require.requireActual(
'react-native/Libraries/Utilities/Platform'
)
Platform.OS = 'android'
return Platform
})
this is the mock you need:
const mockPlatform = OS => {
jest.resetModules();
jest.doMock("Platform", () => ({ OS, select: objs => objs[OS] }));
};
with it you can do the following:
it("my test on Android", () => {
mockPlatform("android");
});
it("my test on iOS", () => {
mockPlatform("ios");
});
That way you can have tests for both platforms
I'm using the solution from this github issue https://github.com/facebook/jest/issues/1370#issuecomment-352597475
I moved the jest config from package.json to separate files.
So far everything seems to work great, including:
a) the right file is imported according to the platform. For example on ios: .ios.tsx, then .native.tsx then .tsx
b) PLATFORM.IOS returns true when running test-ios, no need to mock anything
// package.json
"scripts": {
"test": "cross-env NODE_ENV=test jest --config config/jest.desktop.json",
"test-ios": "cross-env NODE_ENV=test jest --config config/jest.ios.json",
"test-android": "cross-env NODE_ENV=test jest --config config/jest.android.json"
}
// config/jest.web.json
{
...
}
// config/jest.ios.json
{
...
"preset": "react-native",
"haste": {
"defaultPlatform": "ios",
"platforms": [
"android",
"ios",
"native"
],
"providesModuleNodeModules": [
"react-native"
]
},
}
// config/jest.android.json
{
...
"preset": "react-native",
"haste": {
"defaultPlatform": "android",
"platforms": [
"android",
"ios",
"native"
],
"providesModuleNodeModules": [
"react-native"
]
},
}
use jest.doMock and jest.resetModules
jest.resetModules()
jest.doMock('react-native', () => ({ Platform: { OS: 'android' }}))
Maybe the problem in the "import" method, check this:
const isAndroid = require('app/helpers/is_android');
//import isAndroid from 'app/helpers/is_android'
with "import" this will not work, need to use "require".
beforeEach(() => {
jest.resetModules();
});
it("should be true when Android", () => {
jest.mock('Platform', () => {
return { OS: 'android' };
});
expect(isAndroid).toBe(true);
});
import React from "react";
import renderer from "react-test-renderer";
import SmartText from "../SmartText";
describe("markdown smart text component", () => {
beforeEach(() => {
jest.resetModules();
});
it("renders with props on ios", () => {
jest.mock("Platform", () => {
return { OS: "ios" };
});
expect(
renderer.create(<SmartText title="code ios" code />).toJSON()
).toMatchSnapshot();
});
it("renders with props on android", () => {
jest.mock("Platform", () => {
return { OS: "android" };
});
expect(
renderer.create(<SmartText title="code android" code />).toJSON()
).toMatchSnapshot();
});
});
for newer version
"react-native": "0.62.2"
"enzyme": "^3.11.0"
"jest": "24.5.0"
Put it at the top of your test
Object.defineProperty(Platform, 'OS', { get: jest.fn(() => 'ios') })
jest: ^26.5.3
See bottom of this article
import { Platform } from 'react-native';
describe('Android', () => {
it('renders Element if Android', () => {
Platform.OS = 'android';
renderIfAndroid();
expect(wrapper.find(Element)).exists()).toBe(true);
});
});
describe('IOS', () => {
it('renders Element if IOS', () => {
Platform.OS = 'ios';
renderIfIOS();
expect(wrapper.find(Element)).exists()).toBe(true);
});
});
To change Platform only for a specific test, the following can be used:
test('Platform should be Android', () => {
jest.doMock('react-native/Libraries/Utilities/Platform', () => ({
OS: 'android',
}));
expect(Platform.OS).toBe('android');
// restore the previous value 'ios' for Platform.OS
jest.dontMock('react-native/Libraries/Utilities/Platform');
});
You can mock whatever you want from React-Native like this:
describe('notifications actions tests', () => {
let Platform;
beforeEach(() => {
jest.mock('react-native', () => ({
Platform: {
...
}));
Platform = require('react-native').Platform; // incase u would like to refer to Platform in your tests
});
If anyone is looking out to mock Platform.select. The below code can fix your issue.
const mockedData = 'MOCKED-DATA'
jest.mock('react-native', () => ({
Platform: {
select: jest.fn(() => {
return { mockedData } // Your Mocked Value
}),
}
}));
And To mock both OS and Platform. Please refer below code.
jest.mock('Platform', () => ({
OS: 'android', // or 'ios'
select: () => 'mocked-value'
}));
You have to mock the module and import it into your test. Then you can use mockImplementation to set the it to either android or ios
import reactNative from 'react-native';
jest.mock('react-native', () = > jest.fn();
it('is android', () => {
reactNative.mockImplementation(()=>({Platform:{OS: 'android'}}))
//test the android case
})
it('is android', ()=>{
reactNative.mockImplementation(()=>({Platform: { OS: 'io' }}))
//test the ios case
})
OS can be set directly for each test
test('android', () => {
Platform.OS = 'android'
const component = renderer.create(<Component />).toJSON()
expect(component).toMatchSnapshot()
})
test('ios', () => {
Platform.OS = 'ios'
const component = renderer.create(<Component />).toJSON()
expect(component).toMatchSnapshot()
})