How to test 'didFocus' from react navigation with jest - react-native

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

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 clean up React-Native useEffect with axios

Currently I have defined in a functional component a useEffect as below
useEffect(() => {
(async function () {
posts.current = await BlogConsumer.getBlogPosts();
setLoading(false);
})();
return () => {
BlogConsumer.call_controller.abort();
};
}, []);
where this BlogConsumer is defined as below
class BlogConsumer {
static posts = {};
static call_controller = new AbortController();
static async getBlogPosts() {
await axios
.get('https://nice.api', {
signal: this.call_controller.signal,
})
.then(response => {
// treatment for success
})
.catch(error => {
// treatment for erros
});
return this.posts;
}
}
export default BlogConsumer;
The overral ideia is that in the render of the component I'll be calling a static method from my consumer and will retrieve the necessary data. For the pourpuse of not having memory leaks, I have my callback function in my useEffect that will abort my call whenever I unmount the component, but this is not working. React's message of Warning: Can't perform a React state update on an unmounted component. still appears if I enter the component and leave the screen before the API call is finished. I don't know where I am wrong, so I'd like a little help.
Thanks in advance.
You could just cancel the request on unmount. Like this:
export const fetchData = async (signal) => {
try {
const res = await instance.get("/pages/home", {
signal,
});
return res.data;
} catch (error) {
return Promise.reject(error);
}
};
useEffect(() => {
const controller = new AbortController();
fetchData(controller.signal);
return () => {
controller.abort()
};
}, []);

react native unsubscribe event listener

I have a react native component with two event listeners for linking and for dynamicLinks, how do I unsubscribe for both using hooks?
useEffect(() => {
// Update the document title using the browser API
if (Platform.OS === "ios") {
SecurityScreen.enabled(true);
}
// global.perra = "a";
usingAlternativeAPI();
Linking.addEventListener("url", deepLinkHandler);
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the component is unmounted, remove the listener
return () => unsubscribe();
}, []);
Linking lib has a removeEventListener() function you can call with passing the url event type and the handler. This code should work.
useEffect(() => {
// useEffect code here
return function cleanup() {
unsubscribe();
Linking.removeEventListener("url", deepLinkHandler);
};
}, []);
Have you tried this before?
useEffect(() => {
// Update the document title using the browser API
if (Platform.OS === "ios") {
SecurityScreen.enabled(true);
}
// global.perra = "a";
usingAlternativeAPI();
const un = Linking.addEventListener("url", deepLinkHandler);
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
// When the component is unmounted, remove the listener
return () => {
unsubscribe();
un()
}
}, []);
At the moment the documentation points to do this way,
useEffect(() => {
const unsub = Linking.addEventListener("url", ({ url: _url }) => {
setUrl(_url);
});
return unsub.remove();
}, []);

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

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.

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