How to test simulate on `react-navigation`'s navigate? - react-native

I am trying to make better codecoverage by simulating react-navigation's navigate function. Currently, I am having a problem in simulating react-navigation's navigate function.
I've tried to use mockStore from redux-mock-store.
const store = mockStore({
rehydrated: false,
});
const navigation = { navigate: jest.fn() };
const wrapper = shallow(<Login navigation={navigation} />);
Below I have a button (Note that this is a custom button).
<Button
constainerStyle={{ flex: 1 }}
onPress={() => this.goToSignup()}
style={styles.btnSignup}
textStyle={styles.txtSignUp}
>SIGNUP</Button>
goToSignup = () => {
this.props.navigation.navigate('Signup');
}
Below is my test code.
const wrapper = shallow(<Login navigation={navigation} />);
describe('interaction', () => {
beforeEach(() => {
wrapper.setProps({
navigation: {
navigate: jest.fn(),
},
});
});
describe('clicking the button', () => {
let goToSignupSpy;
let onLoginSpy;
let navigateSpy;
beforeEach(() => {
wrapper.instance().goToSignup = jest.fn();
wrapper.instance().onLogin = jest.fn();
goToSignupSpy = jest.spyOn(wrapper.instance(), 'goToSignup');
onLoginSpy = jest.spyOn(wrapper.instance(), 'onLogin');
navigateSpy = jest.spyOn(wrapper.instance().props.navigation, 'navigate');
});
it('should call onLogin callback', () => {
const loginBtn = wrapper.find(Button).at(1);
loginBtn.props().onPress();
expect(onLoginSpy).toBeCalled();
});
it('should call goToSignup callback', () => {
const signupBtn = wrapper.find(Button).at(0);
signupBtn.props().onPress();
expect(goToSignupSpy).toHaveBeenCalled();
/// failing
expect(navigateSpy).toBeCalledWith('Signup');
});
});
afterAll(() => {
Login.prototype.onLogin.mockRestore();
Login.prototype.goToSignup.mockRestore();
});
});
Everything works fine expect spy on react-navigation's navigate. What am I doing wrong?
Expect to simulate mock on react-navigation's navigate function.

Related

Jest testing - React Native - NetInfo + fire event is it possible to test with Jest?

I have this simple component and want to test it with Jest, how (and if) is it possible to mock addEventListener and then fire the event with my mock data?
My goal is to test the modal shows when there is no internet connection (triggered by net info event change) in the complex component, so there is no option for passing the visibility as props.
import {addEventListener} from '#react-native-community/netinfo'
...
export const NoInterenetModal = () => {
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const unsubscribeNetInfo = addEventListener(async (state) => {
setModalVisible(!state.isInternetReachable);
})
return () => {
unsubscribeNetInfo();
}
}, [])
}
I tried something like this,but no success:
import {testMatchSnapshot} from '../tests/testUtils'
import {NoInterenetModal} from './NoInterenetModal'
jest.mock('#react-native-community/netinfo', () => ({
addEventListener: (fn: (input: any) => void) => {
fn({isInternetReachable: false})()
},
useNetInfo: () => ({
isConnected: false,
}),
}))
describe('Component: NoInterenetModal', () => {
// here fire event?
testMatchSnapshot(<NoInterenetModal />)
})

How to pass the parameter to another screen using axios?

I'm doing the verification of the phone number, and I have to pass the phone number to the other checkCode.js component.
I have seen examples that pass it navigate() as a pramas, but how can I receive it in another component.
register.js
const SignUp = ({ navigation }) => {
const [phoneNumber, setPhoneNumber] = useState('');
let register = "https://app.herokuapp.com/api/v1/auth/register"
let sendVerification = "https://app.herokuapp.com/api/v1/auth/sendVerification-otp"
const signUp = () => {
const userParams = {
phone: phoneNumber,
};
const requestOne = axios.post(register, userParams)
const requestTwo = axios.post(sendVerification, userParams)
axios
.all([requestOne, requestTwo], userParams)
.then(axios.spread((...responses) => {
navigation.navigate('CodeVerification')
}))
.catch((err) => {
console.log('the error:', err.message);
})
}
checkCode.js
export default function CodeVerification({navigation}) {
//need phoneNumber param in this component
const [code, setCode] = useState('');
const confirm = () =>{
const userParams = {
phone: "+11111111",
code:code,
};
axios
.post('https://app.herokuapp.com/api/v1/auth/sendVerification-otp', userParams)
.then((response) =>{
console.log('response', response.data);
navigation.navigate('Welcome')
})
.catch((error) => {
console.log('the error:', error.message);
});
};
How can I pass it?
This might help
register.js
const SignUp = ({ navigation }) => {
// existing code remains the same
const signUp = () => {
....
axios
.all([requestOne, requestTwo], userParams)
.then(
axios.spread((...responses) => {
// send params like this
navigation.navigate("CodeVerification", {phone: phoneNumber});
})
)
.catch((err) => {
console.log("the error:", err.message);
});
};
};
checkCode.js
export default function CodeVerification({ route, navigation }) {
// get phoneNumber from props
const {phone} = route.params; // UPDATED this line
const [code, setCode] = useState("");
....
}
You can use Context Api
Context api is commonly used for transferring data to another component.

How to use focus and blur listener in single useEffect react native

As you know in useEffect we return the unsubscribe at the end if we assign any listener to unsubscribe const as shown under
As we Using
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
})
return unsubscribe;
}, [navigation]);
As I want
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
})
const unsubscribe2 = navigation.addListener('blur', () => {
// code
})
// need to return both listeners
}, [navigation]);
You can cleanup like this
useEffect(() => {
navigation.addListener('focus', handler)
navigation.addListener('blur', handler)
return () => {
navigation.removeListener('focus', handler)
navigation.removeListener('blur', handler)
}
},[navigation])
The official example here https://reactjs.org/docs/hooks-effect.html#effects-with-cleanup
I didn't test this, but you might be able to do something like this:
useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// code
});
const unsubscribe2 = navigation.addListener('blur', () => {
// code
});
return () => {
// executed when unmount
unsubscribe();
unsubscribe2();
}
}, [navigation]);

How to test 'didFocus' from react navigation with jest

I have a component that looks like this
async componentDidMount() {
const { navigation } = this.props
this.subs = [
navigation.addListener('didFocus', () => this.onComponentFocus()),
]
}
onComponentFocus() {
const { dispatch } = this.props
dispatch(fetchDevices())
}
Now i want to write a test that chekcs fetchDevice got called once. The first idea was to mock Navigation like this
const navigation = {
navigate: jest.fn(),
}
But now how do I check this.subs and how do i check fetchDevices got fired?
If we suppose that fetchDevices comes from a library
Component.spec.js
import fetchDevices from 'device-fetcher';
jest.mock('device-fetcher');
// as your component accepts the dispatch function
// you can create it as mock function
const mockDispatch = jest.fn();
// since in your implementation you're calling navigation.addListener
const mockNavigation = {
navigate: jest.fn(),
// it should also have
addListener: jest.fn()
};
describe('Component', () => {
const wrapper = shallow(<Component navigation={mockNavigation} dispatch={mockDispatch} />);
describe('navigation didFocus', () => {
beforeAll(() => {
// get .addEventListener calls with 'didFocus'
mockNavigation.addEventListener.mock.calls
.filter(([eventName]) => eventName === 'didFocus')
// iterate over the "attached" handlers
.map(([eventName, eventHandler]) => {
// and trigger them
eventHandler();
});
});
it('should have called the dispatch with the result of fetchDevices', () => {
expect(mockDispatch).toHaveBeenCalledWith(
fetchDevices.mock.results[0].value
);
});
});
});
note: it's not tested, just a solution outline
edit: if the fetchDevices is a property instead of mocking the library you define a mock function
const fetchDevices = jest.fn();
// and pass it to the component
shallow(<Component navigation={mockNavigation} dispatch={mockDispatch} fetchDevices={fetchDevices} />);
and then you should have the same assertions for it

React Native/Jest TypeError: Cannot read property 'params' of undefined - testing with jest

I'm trying to create a test in an application with jest and this is some lines of my code:
import React, { Component } from 'react';
import {...} from 'react-native';
import jwt_decode from 'jwt-decode';
class CreateProduct extends Component {
constructor(props) {
super(props);
this.keyboardHeight = new Animated.Value(0);
this.imageHeight = new Animated.Value(199);
this.state = {
isButtonsHidden: false,
title: '',
price: '',
description: '',
isDialogVisible: false,
messageError: '',
};
}
_goBack = async () => {
const {state} = this.props.navigation;
var token = state.params ? state.params.token : undefined;
this.props.navigation.navigate('MyProducts', {token:token});
}
I want to test the navigation:
this.props.navigation.navigate('MyProducts', {token:token});
Now this is the attempt to test:
describe('Testing navigation', () =>{
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation:{
navigate: spyNavigate
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props}/>)
wrapper.setState({params: params})
})
it('should test navigation', () => {
wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
But I'm receiving this error.
I'm assuming that there is an error with the way I'm passing the const params. Can you help me telling what's the best way I can do this to simulate a token and that way I can navigate in the screen?
Thanks.
Rootcause is your _goBack is async. But you don't await till it ends before running expect. Even more: jest also does not wait _goBack to finish so you don't even see an error
Cannot read property 'params' of undefined
that happens because you don't mock state in navigation.params.
To work with async code there are 2 different approaches in Jest: either returning Promise from the it() or running done() callback manually(it's passed as 1st argument in it()).
I'll picking 2nd since it allows us also await until goBack is finished before running expect:
describe('Testing navigation', () => {
let wrapper = null
const spyNavigate = jest.fn()
const props = {
navigation: {
navigate: spyNavigate,
state: {}
}
}
const params = {
token: 'randomToken'
}
beforeEach(() => {
wrapper = shallow(<CreateProduct {...props} />)
wrapper.setState({ params: params })
})
it('should test navigation', async () => {
await wrapper.instance()._goBack(params)
expect(spyNavigate).toHaveBeenCalled()
})
})
Or without using async/await it would look like
it('should test navigation', () => {
return wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled());
})
that looks messy
Or using done() callback
it('should test navigation', (done) => {
wrapper.
instance()._goBack(params).
then(() => expect(spyNavigate).toHaveBeenCalled()).
then(done);
})