How to mock React Functionnal Component method? - react-native

I don't found a lot of jest&enzyme mock/spy example when react components are functions instead of class...
Since it looks impossible to spy on functionnal components method with "jest.spyOn" like it's possible to do with class components, is there a better or other way than passing method reference in props like my example below ?
it('click on connect button', () => {
let handleConnect = jest.fn();
let shallowLogin = shallow(<Login handleConnect={handleConnect} />);
//const spy = jest.spyOn(shallowLogin.instance(), 'handleConnect');
//Impossible to spyOn like this ?
let button = shallowLogin.find('Styled(Button)');
button.simulate('press');
expect(handleConnect).toHaveBeenCalledTimes(1);
});
I hate to do this because it's intrusive on the component code and it force the change stuff inside for the tests...
Component example :
import useForm from 'react-hook-form';
export default function Login(props) {
const {register, handleSubmit, setValue} = useForm();
const onSubmit = data => {
console.log('data: ', data);
if (FormValidation(data, "login")) {
props.navigation.navigate('App');
}
};
return (
...
<Button style={styles.button} rounded large onPress={handleSubmit(onSubmit)}><Text
style={styles.buttonText}>{locale.t('loginScreen.CONNECT')}</Text></Button>
...
);
}

Related

How to set state in component remotely on callback?

I am making a react native app which receives data from ws api on callback.
This function resides in api module and look like this:
ws.onmessage = (e) => { //callbnack to HOME
const request = JSON.parse(e.data)
...
}
I want to show received text on screen in Component. It has state with text field, and <Text> {this.state.screenText} </Text> element.
Both modules imported in Main screen component. How do I do this?
1. I think you should have to create a custom hook or context provider if you are not calling this API on the Main screen component
2. If you call this API function on the main screen then you should create a simple function eg. ABC(data) that will receive the data from API and pass it to the function that you are calling as a parameter. On the other side when API gives some responses call this function which you are sending as a parameter and pass the data to it.
e.g:
export const sendData = (e, ABC) => { //callbnack to HOME
const request = JSON.parse(e.data)
ABC(request)
...
}
On Mian Screen :
export {sendData} .....
function ABC(data){console.log(data)
}
sendData(e, abc)
Use state management library like redux, zustand, etc. Here's an example with zustand -
store.js
export const useMyStore = create(() => ({
text: 'hello',
}));
export const setText = (text) => useMyStore.setState({ text });
websocket.js
import { setText } from './store.js';
ws.onmessage = (e) => {
const request = JSON.parse(e.data)
setText(request.text);
...
}
Main.js
import { useMyStore } from './store.js';
function Main() {
const text = useMyStore((state) => state.text);
return (
<View>
<Text>{text} </Text>
</View>
);
}

Passing a function in parent, and params in child

How can I pass a function in the parent component, but then have parameters inserted into that function from within the child? For example, here is my code:
parent.js
<MyComponent
getData={myGetFunction()}
saveData={mySaveFunction()}
/>
child.js
const onRefresh = useCallback(async (getData, saveData) => {
const Response = await props.getData(props.config); // I want myGetFunction(props.config)
props.dispatch( props.saveData(Response) ); // I want mySaveFunction(Response)
}, []);
In this code, the two passed functions would look like this:
myGetFunction()(props.config)
mySaveFunction()(Response)
Of course this is no good. So how do I get this:
myGetFunction(props.config)
mySaveFunction(Response)
What you want is only possible if you rename the props for MyComponent.
function myGetFunction(config) {
}
function mySaveData(response) {
}
<MyComponent
myGetFunction={myGetFunction}
mySaveData={mySaveData}
/>
Then use prop destructuring in your child.
const MyComponent = ({myGetFunction, mySaveData}) => {
const onRefresh = useCallback(async (getData, saveData) => {
const Response = await myGetFunction(props.config)
props.dispatch( mySaveFunction(Response) )
}, []);
...
}
If myGetFunction is a function that returns the function that you want to use, then you can still do this, but call it like you did.
<MyComponent
myGetFunction={myGetFunction()}
mySaveData={mySaveData()}
/>
The function call in the child is identical.
If you cannot rename the props for some reasons, then you can assign it to a constant and reuse it.
<MyComponent
getData={myGetFunction}
saveData={mySaveFunction}
/>
const MyComponent = ({getData, saveData}) => {
const myGetFunction = getData
const mySaveFunction = saveData
...
}

Jest Testing onTouchStart/onTouchEnd

I've got a component that uses the onTouchStart and onTouchEnd and I can't figure out how to test it.
Here's a snack for the code, but the snippet is below:
export default function TouchComponent({
children,
onStart = doNothing,
onEnd = doNothing
}: TouchComponentProps): React.ReactElement {
return (
<View
onTouchStart={(e) => onStart()}
onTouchEnd={(e) => onEnd()}
>{children}</View>
);
}
const doNothing = () => {};
interface TouchComponentProps {
children?: React.ReactNode;
onStart?: () => void;
onEnd?: () => void;
}
Looking at the documentation, there aren't any methods listed for onTouchStart/onTouchEnd, but this method array seems to suggest that it has other methods that can be invoked, but it doesn't look like it works here because fireEvent["onTouchStart"](myComponent); fails with an error saying: TypeError: _reactNative.fireEvent.onTouchStart is not a function.
I've searched around but can't seem to find any documentation or other questions about testing onTouchStart and onTouchEnd, so how do I fire these events?
I figured this out almost immediately after asking this question. In my situation, I can invoke them like this:
import TouchComponent from "../TouchComponent ";
import { render, fireEvent } from "#testing-library/react-native";
it("Invokes onTouchStart and onTouchEnd", () => {
const { getByTestId } = render(
<TouchComponent
onStart={() => {
console.log("onTouchStart invoked");
}}
onEnd={() => {
console.log("onTouchEnd invoked");
}}
testID="my-component" />);
const myComponent = getByTestId("my-component");
// Passing empty {} data, but may need to supply for other use cases.
fireEvent(myComponent, "onTouchStart", {});
fireEvent(myComponent, "onTouchEnd", {};
});
// Console:
// console.log
// onTouchStart invoked
// console.log
// onTouchEnd invoked

How to call useQuery as an async task

I am new to React Native and Apollo-Client. In my Screen there are three tabs and individual tabs is calling its own data. But due to useQuery UI is completely freezing and giving a very bad experience.
Is there any other way that I can call the useQuery in async manner or in any background task?
EDIT
function PlayerList(props) {
const [loading , setloading] = useState(true)
const [data, setData] = useState()
const [loadData, { tempLoading, tempError, tempData }] = useLazyQuery(query);
async function getData(){
loadData()
}
useEffect(() => {
if (tempData == undefined) {
getData()
}
})
if(tempLoading){
return
<View >....</View>
}
if(tempData) {
return ( <View> ... </View>) }
}
Please find the above code for better understanding.
Apollo Client uses fetch which is always asynchronous. The issue is that you are creating an infinite loop.
The object returned by the useLazyQuery hook you are destructuring does not have properties named tempLoading, tempError or tempData. See here for how to correctly rename variables when using destructuring syntax.
Because tempData is always undefined, your useEffect hook's callback will be called on every render.
Calling loadData on every render triggers another render.
By using useLazyQuery, you are unnecessarily complicating your code -- you should be using useQuery instead.
const { data, loading } = useQuery(query)
if (loading) {
return <View>....</View>
}
return <View>...</View>

Passing props to a component to use in testing a SnapShot

Screen-test.js
it('renders the Engagement Detail modal', () => {
const tree = renderer.create(<EngagementDetailModal details={engagementData}/>).toJSON();
expect(tree).toMatchSnapshot();
})
the component I am trying to test
class CompanyDetailView extends React.Component {
render() {
const data = this.props.navigation.getParam('details');
return (
// rest of the code
)
}
}
My data variable is just a bunch of static data. Using jest I am getting this error TypeError: Cannot read property 'getParam' of undefined and it is pointing to this line const data = this.props.navigation.getParam('details');
I know the component works as i run the app, just the test is failing.
I thought that by just providing the prop to the component in my Screen-test.js file it would work the same but i get undefined. How can i test this?
Also I am passing the prop using react navigation like this onPress={() => this.props.navigation.navigate('EngagementDetailModal', {details: this.props.data})
It cannot read the property getParamof navigation because you haven't mocked the navigation. So when the test case runs, it doesn't know what this.props.navigation does.
Add the following after imports section in your Screen-test.js .
const testProps = props => ({
navigation: {navigate: jest.fn(), getParam : jest.fn()},
...props,
})
Hope this helps!!