This is the component code which I am going to test.
Here I used one state hook setCheck.
import React, { SyntheticEvent, useEffect, useState } from 'react';
import { Checkbox, Grid, Header } from 'semantic-ui-react';
interface IIdentityItem {
name: string,
comment: string,
checked: boolean,
handleSetCheckState: any,
index: number
};
export default ({ name, comment, checked, handleSetCheckState, index }: IIdentityItem) => {
const [check, setCheck] = useState(true);
useEffect(() => {
setCheck(checked);
}, []);
const onChange = (e: SyntheticEvent, data: object) => {
setCheck(!check);
handleSetCheckState(index, !check);
};
return (
<Grid className='p-16 py-9 bg-white'>
<Grid.Column width='eleven' textAlign='left'>
<Header as='p' className='description'>{name}</Header>
<Header as='p' className='comment'>{comment}</Header>
</Grid.Column>
<Grid.Column width='five' verticalAlign='middle'>
<Checkbox toggle checked={check} onChange={onChange} />
</Grid.Column>
</Grid>
)
}
This is the jest unit test code.
import ChainItem from './ChainItem';
import React from 'react';
import { create, act } from 'react-test-renderer';
import { BrowserRouter } from 'react-router-dom';
import { Checkbox } from 'semantic-ui-react';
const useStateSpy = jest.spyOn(React, "useState");
describe('ChainItem', () => {
let handleSetCheckState, index;
beforeEach(() => {
handleSetCheckState = jest.fn();
index = 0;
//useStateSpy.mockReturnValueOnce([true, setCheck]);
});
it('should work', () => {
let tree;
act(() => {
tree = create(
<ChainItem
handleSetCheckState={handleSetCheckState}
index={index} />
);
});
expect(tree).toMatchSnapshot();
});
it('functions ', () => {
let setCheck = jest.fn();
useStateSpy.mockImplementationOnce(function() { return [true, setCheck] });
let tree;
act(() => {
tree = create(
<ChainItem
handleSetCheckState={handleSetCheckState}
checked={true}
index={index} />
);
});
const items = tree.root.findAllByType(Checkbox);
act(() => items[0].props.onChange({
target: {
value: false
}
}));
expect(setCheck).toHaveBeenCalled();
expect(handleSetCheckState).toHaveBeenCalledWith(
index,
false
);
});
afterAll(() => jest.resetModules());
});
But setCheck is not getting called.
What have I done wrong?
Add Unit Test for React components, but still not working with react hook testings.
I am facing an error TypeError: Cannot read property 'dispatchEvent' of null when trying to run an enzyme test to simulate onGridReady of an ag-grid table.
My questions and places where I am getting stuck...
How do I gracefully test the onGridReady function properly so that it doesn't error out and I can test that it has been called once ?
How do I test the other function onGridSizeChanged?
The Coverage report says I am missing the line setTableData to test - how do I test that line ?
This is how myfiles look
myTable.tsx
imports...
const myTable: React.FC = () => {
//intialize state variables
const onGridReady = ({ api }: { api: GridApi }) => {
setGridApi(api)
api.sizeColumnsToFit()
api.setHeaderHeight(10)
}
const onGridSizeChanged = () => {
gridApi?.sizeColumnsToFit()
}
useEffect(() => {
if (myTableData) {
if (selectedCountryName !== '') {
setTableData(objectCopy(myTableData))
}
}
}, [myTableData, selectedCountryName])
useEffect(() => {
// do some stuff
}, [businessDate])
return <div className={'my-table'}>
<Grid container spacing={12}>
<div>
<h1/>Title</h1>
</div>
<Grid item xs={12}>
<div data-testid="my-table-grid">
<AgGridReact
containerProps={{ style: { height: `${containerHeight}px` } }}
columnDefs={myTableColumns(moment(fromDate), moment(toDate))}
onGridReady={onGridReady}
onGridSizeChanged={onGridSizeChanged}
onModelUpdated={onGridSizeChanged}
rowData={tableData}
/>
</div>
</Grid>
</Grid>
</div >
}
export default myTable
myTable.test.tsx
import myTable from './myTable'
// other imports...
Enzyme.configure({ adapter: new Adapter() })
const onGridReady = jest.fn()
function setUpMyTableComponent() {
const wrapper = shallow(
<Provider store={store}>
<myTable >
<ColumnLayout>
<ColumnLayout>
<DataGrid onGridReady={onGridReady} />
</ColumnLayout>
</ColumnLayout>
</myTable >
</Provider>
)
return { wrapper }
}
describe('myTable', () => {
it('checking myTable', () => {
const { wrapper } = setUpMyTableComponent()
expect(wrapper).toBeDefined
expect(wrapper.find('.my-table')).toBeDefined
})
it('simulate onGridReady', async () => {
const { wrapper } = setUpMyTableComponent()
const aggrid = wrapper.find(AgGridReact)
dataGrid.simulate('onGridReady')
await tick()
expect(onGridReady).toHaveBeenCalled()
})
function tick() {
return new Promise(resolve => {
setTimeout(resolve, 0);
})
}
})
Problem is that my component content rendering in an infinite loop.
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false)
useEffect(
() => {
useAsyncStorage.getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
useAsyncStorage.removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return(
<View>
<DrawerHeader username = 'Danish hello' /> // Custom drawer header
<View style={styles.DrawerBody}>
<CustomDrawerLink name="Home" iconName='home' navigationScreen='HomeScreen' navigation={navigation} />
{
logged_in ?
<View>
<CustomDrawerLink name="Profile" iconName='user' /> // Drawer custom buttons
<CustomDrawerLink name="Cart" iconName='hamburger' />
</View>
: undefined
}
<Divider/>
{
logged_in ?
<TouchableOpacity style={{flexDirection:'row' , alignItems:'center'}} onPress={()=>{SignOut()}} >
<FontAwesome5 name='sign-out-alt' style={{fontSize:20, marginRight:10 , color:COLORS.red_color, width:35}} />
<Text style={{fontSize:16 , color:'gray'}}>Sign Out</Text>
</TouchableOpacity>
:
<View>
<CustomDrawerLink name="Sign In" iconName='sign-in-alt' navigationScreen='LoginScreen' navigation={navigation} />
<CustomDrawerLink name="Create New Account" iconName='user-plus' navigationScreen='RegisterScreen' navigation={navigation} />
</View>
}
</View>
</View>
)
}
Edited part
ADDED PARENT COMPONENT Here it is
As you mentioned i am using useEffect Hook in my parent component, Here is the code
here i am making Drawer (Side navigation bar)
const Drawer = createDrawerNavigator()
Here is App component
const App = () =>{
const network = useNetInfo()
const [Scren_navigation , setNavigation] = useState('')
const [activeDrawer , setActiveDrawer] = useState(<EmptyDrawer/>)
const UpdateScreen = (props) =>{
setNavigation(props.navigation)
return activeDrawer
}
useEffect(()=>{
setTimeout(() => {
network.isInternetReachable ? setActiveDrawer(<CustomDrawer navigation={Scren_navigation} />) : setActiveDrawer(<NoInternetDrawer navigation={Scren_navigation} />)
}, 5000);
})
return(
<NavigationContainer>
<Drawer.Navigator drawerContent={({navigation}) => <UpdateScreen navigation={navigation} /> } >
<Drawer.Screen name="StackNavigation" component={Navigation} />
</Drawer.Navigator>
</NavigationContainer>
)
}
I have added parent Component, Please let me know where i was doing wrong things
The package provides two API's one with AsyncStorage and the other one is useAsyncStorage. Both have different usage patterns, you have mixed both in your snippet. Checkout the code below for example usage of each API.
AsyncStorage
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false)
useEffect(
() => {
AsyncStorage.getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
AsyncStorage.removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return ...;
}
UseAsyncStorage
const CustomDrawer =({navigation})=>{
const [logged_in , setLoggedIn]= useState(false);
const { getItem, setItem, removeItem } = useAsyncStorage('#storage_key');
useEffect(
() => {
getItem('auth_token')
.then((token) => {
if(token){
setLoggedIn(true)
}
})
} , []
)
const SignOut = ()=>{
removeItem('auth_token')
.then(()=>{
setLoggedIn(false)
})
}
return ...
}
The second problem is caused by the parent component because of a missing [] argument to the useEffect:
const App = () => {
const network = useNetInfo()
const [isRunningAvailabilityCheck, setIsRunningAvailabilityCheck] = useState(true);
const [internetIsAvailable, setInternetIsAvailable] = useState(true);
useEffect(() => {
setTimeout(() => {
setInternetIsAvailable(network.isInternetReachable);
setIsRunningAvailabilityCheck(false);
}, 5000);
}, []);
return (
<NavigationContainer>
<Drawer.Navigator drawerContent={({navigation}) => {
if(isRunningAvailabilityCheck){
return <EmptyDrawer/>;
}
if(internetIsAvailable){
return <CustomDrawer navigation={navigation} />
}
return <NoInternetDrawer navigation={navigation} />
}}>
<Drawer.Screen name="StackNavigation" component={Navigation}/>
</Drawer.Navigator>
</NavigationContainer>
)
}
Async storage Docs
Is anyone using react-native-modalize module?
react-native-modalize module, when i render code on flatListProps object its showing the error below!!
Here is the example as well https://jeremybarbet.github.io/react-native-modalize/#/EXAMPLES
import React, { useRef } from 'react';
import { View, Text, TouchableOpacity } from 'react-native';
import { Modalize } from 'react-native-modalize';
export const App = () => {
const modalizeRef = useRef<Modalize>(null);
const onOpen = () => {
modalizeRef.current?.open();
};
const arrayData = [ { "heading": "test"}, {"heading": "test2"}... ]
let data = { "object": [arrayData] }
const getData = () => ({ data });
const renderItem = (item) => (
<View>
<Text>{item.heading}</Text>
</View>
);
return (
<>
<TouchableOpacity onPress={onOpen}>
<Text>Open the modal</Text>
</TouchableOpacity>
<Modalize
ref={modalizeRef}
flatListProps={{
data: getData(),
renderItem: renderItem,
keyExtractor: item => item.heading,
showsVerticalScrollIndicator: false,
}}
/>
</>
);
}
data of flatListProps is an array object.
let data = { "object": [arrayData] }
const getData = () => ([ ...data.object ]);
I am trying to write test for this component using jest
import { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Query } from 'react-apollo';
import { updateYourDetails } from 'universal/domain/health/yourDetails/yourDetailsActions';
import Input from 'universal/components/input/input';
import InputNumber from 'universal/components/input/inputNumber/inputNumber';
import AsyncButton from 'universal/components/asyncButton/asyncButton';
import ErrorMessage from 'universal/components/errorMessage/errorMessage';
import Link from 'universal/components/link/link';
import analytics from 'universal/utils/analytics/analytics';
import { isChatAvailable } from 'universal/logic/chatLogic';
import { validators } from 'universal/utils/validation';
import { localTimezone, getWeekdays } from 'universal/utils/date';
import {
CALL_ME_BACK_LOADING_MSG,
CALL_ME_BACK_LABELS_SCHEDULE_TIME,
CALL_ME_BACK_LABELS_SELECTED_DATE,
CALL_ME_BACK_ERROR_MSG,
CALL_ME_BACK_TEST_PARENT_WEEKDAY,
CALL_ME_BACK_TEST_CHILD_WEEKDAY,
} from 'universal/constants/callMeBack';
import CallCenterAvailibility from './CallCenterAvailibility';
import SelectWrapper from './SelectWrapper';
import SelectOption from './SelectOption';
import styles from './callMeBackLightBox.css';
import { CALL_ME_BACK_QUERY } from './callMeBackQuery';
import postData from './postData';
export const CallMeForm = props => {
const initSelectedDate = getWeekdays()
.splice(0, 1)
.reduce(acc => ({ ...acc }));
const { onSubmissionComplete, className, variant } = props;
const [hasSuccessfullySubmitted, setHasSuccessfullySubmitted] = useState(false);
const [apiStatus, setApiStatus] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [cellNumber, setCallNumber] = useState(props.cellNumber || '');
const [customerFirstName, setCustomerFirstName] = useState(props.customerFirstName || '');
const [number, setNumber] = useState(props.Number || '');
const [selectedDate, setSelectedDate] = useState(initSelectedDate || '');
const [scheduledTime, setScheduledTime] = useState('');
const weekdays = getWeekdays() || [];
const timezone = localTimezone || '';
const requestReceived = apiStatus === 'CALLBACK_ALREADY_EXIST';
const cellNumberInput = useRef(null);
const customerFirstNameInput = useRef(null);
const getQuery = () => (
<Query query={CALL_ME_BACK_QUERY} variables={{ weekday: selectedDate.weekday }}>
{({ data, error, loading }) => {
if (loading)
return (
<SelectWrapper disabled labelTitle={CALL_ME_BACK_LABELS_SCHEDULE_TIME} name="scheduledTime">
<SelectOption label={CALL_ME_BACK_LOADING_MSG} />
</SelectWrapper>
);
if (error) return <ErrorMessage hasError errorMessage={<p>{CALL_ME_BACK_ERROR_MSG}</p>} />;
return (
<CallCenterAvailibility
selectedDate={selectedDate}
callCenterBusinessHour={data.callCenterBusinessHour}
onChange={val => setScheduledTime(val)}
/>
);
}}
</Query>
);
const getPostSubmitMessage = (firstName: string, type: string) => {
const messages = {
callCentreClosed: `a`,
requestReceived: `b`,
default: `c`,
};
return `Thanks ${firstName}, ${messages[type] || messages.default}`;
};
const validate = () => {
const inputs = [customerFirstNameInput, cellNumberInput];
const firstInvalidIndex = inputs.map(input => input.current.validate()).indexOf(false);
const isValid = firstInvalidIndex === -1;
return isValid;
};
const onSubmitForm = event => {
event.preventDefault();
onSubmit();
};
const onSubmit = async () => {
if (variant === '0' && !validate()) {
return;
}
analytics.track(analytics.events.callMeBack.callMeBackSubmit, {
trackingSource: 'Call Me Form',
});
setIsLoading(true);
const srDescription = '';
const response = await postData({
cellNumber,
customerFirstName,
number,
scheduledTime,
timezone,
srDescription,
});
const { status } = response;
const updatedSubmissionFlag = status === 'CALLBACK_ALREADY_EXIST' || status === 'CALLBACK_ADDED_SUCCESSFULLY';
// NOTE: add a slight delay for better UX
setTimeout(() => {
setApiStatus(apiStatus);
setIsLoading(false);
setHasSuccessfullySubmitted(updatedSubmissionFlag);
}, 400);
// Update Redux store
updateYourDetails({
mobile: cellNumber,
firstName: customerFirstName,
});
if (onSubmissionComplete) {
onSubmissionComplete();
}
};
if (hasSuccessfullySubmitted) {
return (
<p aria-live="polite" role="status">
{getPostSubmitMessage(
customerFirstName,
(!requestReceived && !isChatAvailable() && 'callCentreClosed') || (requestReceived && 'requestReceived')
)}
</p>
);
}
return (
<form onSubmit={onSubmitForm} className={className}>
{variant !== '1' && (
<>
<label htmlFor="customerFirstName" className={styles.inputLabel}>
First name
</label>
<Input
className={styles.input}
initialValue={customerFirstName}
isMandatory
maxLength={20}
name="customerFirstName"
onChange={val => setCustomerFirstName(val)}
ref={customerFirstNameInput}
value={customerFirstName}
{...validators.plainCharacters}
/>
</>
)}
{variant !== '1' && (
<>
<label htmlFor="cellNumber" className={styles.inputLabel}>
Mobile number
</label>
<Input
className={styles.input}
initialValue={cellNumber}
isMandatory
maxLength={10}
name="cellNumber"
onChange={val => setCallNumber(val)}
ref={cellNumberInput}
type="tel"
value={cellNumber}
{...validators.tel}
/>
</>
)}
{variant !== '1' && (
<>
{' '}
<label htmlFor="number" className={styles.inputLabel}>
Qantas Frequent Flyer number (optional)
</label>
<InputNumber
className={styles.input}
disabled={Boolean(props.number)}
initialValue={number}
name="number"
onChange={val => setNumber(val)}
value={number}
/>
</>
)}
{weekdays && (
<>
<SelectWrapper
testId={`${CALL_ME_BACK_TEST_PARENT_WEEKDAY}`}
labelTitle={CALL_ME_BACK_LABELS_SELECTED_DATE}
name="selectedDate"
onChange={val =>
setSelectedDate({
...weekdays.filter(({ value }) => value === val).reduce(acc => ({ ...acc })),
})
}
tabIndex={0}
>
{weekdays.map(({ value, label }, i) => (
<SelectOption
testId={`${CALL_ME_BACK_TEST_CHILD_WEEKDAY}-${i}`}
key={value}
label={label}
value={value}
/>
))}
</SelectWrapper>
{getQuery()}
</>
)}
<AsyncButton className={styles.submitButton} onClick={onSubmit} isLoading={isLoading}>
Call me
</AsyncButton>
<ErrorMessage
hasError={(apiStatus >= 400 && apiStatus < 600) || apiStatus === 'Failed to fetch'}
errorMessage={
<p>
There was an error submitting your request to call you back. Please try again or call us at{' '}
<Link href="tel:134960">13 49 60</Link>.
</p>
}
/>
</form>
);
};
CallMeForm.propTypes = {
cellNumber: PropTypes.string,
customerFirstName: PropTypes.string,
number: PropTypes.string,
onSubmissionComplete: PropTypes.func,
className: PropTypes.string,
variant: PropTypes.string,
};
const mapStateToProps = state => {
const { frequentFlyer, yourDetails } = state;
return {
cellNumber: yourDetails.mobile,
customerFirstName: yourDetails.firstName,
number: frequentFlyer.memberNumber,
};
};
export default connect(mapStateToProps)(CallMeForm);
My test file is as below
import { render, cleanup } from '#testing-library/react';
import { MockedProvider } from 'react-apollo/test-utils';
import { shallow } from 'enzyme';
import MockDate from 'mockdate';
import { isChatAvailable } from 'universal/logic/chatLogic';
import { CALL_ME_BACK_QUERY } from './callMeBackQuery';
import { CallMeForm } from './CallMeForm';
import postData from './postData';
jest.mock('universal/components/input/input', () => 'Input');
jest.mock('universal/components/asyncButton/asyncButton', () => 'AsyncButton');
jest.mock('universal/components/errorMessage/errorMessage', () => 'ErrorMessage');
jest.mock('universal/logic/chatLogic');
jest.mock('./postData');
describe('CallMeForm', () => {
let output;
beforeEach(() => {
jest.resetModules();
jest.resetAllMocks();
const mockQueryData = [
{
client:{},
request: {
query: CALL_ME_BACK_QUERY,
variables: { weekday: '' },
},
result: {
data: {
callCenterBusinessHour: {
timeStartHour: 9,
timeStartMinute: 0,
timeEndHour: 5,
timeEndMinute: 0,
closed: false,
},
},
},
},
];
const { container } = render(<MockedProvider mocks={mockQueryData} addTypename={false}><CallMeForm /></MockedProvider>);
output = container;
});
afterEach(cleanup);
it('renders correctly', () => {
expect(output).toMatchSnapshot();
});
});
I keep getting error: TypeError: this.state.client.stop is not a function
I also removed <MockedProvider> wrapper and I got another error Invariant Violation: Could not find "client" in the context or passed in as a prop. Wrap the root component in an , or pass an ApolloClient instance in
via props.
Does anyone know why I get this error and how to fix this?
I have not the solution, but I've got some information.
First of all, I'm having the same error here, rendering with #testing-library/react.
I then tried to render with ReactDOM, like that:
// inside the it() call with async function
const container = document.createElement("div");
ReactDOM.render(
< MockedProvider {...props}>
<MyComponent />
</MockedProvider>,
container
);
await wait(0);
expect(container).toMatchSnapshot();
And also tried to render with Enzyme, like that:
// inside the it() call, with async function too
const wrapper = mount(
<MockedProvider {...props}>
<MyComponent />
</MemoryRouter>
);
await wait(0);
expect(wrapper.html()).toMatchSnapshot();
Both ReactDOM and Enzyme approaches worked fine.
About the error we're getting, I think maybe it's something related with #testing-library/react =/
I didn't tried to render with react-test-renderer, maybe it works too.
Well, that's what I get... maybe it helps you somehow.
Ps.: About waait: https://www.apollographql.com/docs/react/development-testing/testing/#testing-final-state
EDIT 5 Feb 2020:
Based on https://github.com/apollographql/react-apollo/pull/2165#issuecomment-478865830, I found that solution (it looks ugly but works ¯\_(ツ)_/¯):
<MockedProvider {...props}>
<ApolloConsumer>
{client => {
client.stop = jest.fn();
return <MyComponent />;
}}
</ApolloConsumer>
</MockedProvider>
I had the same problem and was able to solve it. I had a missing peer dependency.
Your package.json is not shown so I am not sure if your problem is the same as mine but I was able to resolve the problem by installing "apollo-client".
I am using AWS Appsync for my client and hence did not have apollo-client installed.