how to test useEffect setInterval with react testing? - reactjs

how to test if i give isSuccess true at 1 second ?
(when loading screen is done)
const [state, setState]= useState({
isLoading: true,
isSuccess: false
})
useEffect(() => {
setInterval(() => {
setState({
isSuccess:true
});
}, 1000)
});
if (!state.isSuccess) {
return <p data-testid='fetching-data' className='text-center'>"loading..."</p>
}
I tried like this but it doesn't work :
test('isSuccess true', async()=>{
render(<App/>)
const ele = screen.getByText(/learn react/i)
await waitFor(() => expect(ele).toBeInTheDocument())
})

Related

How to test the useEffect hook using Jest

How can we initite the useEffect using jest to wite the test cases.
let initailState = {
loading: false,
card: [],
welcomd: true
}
const helloWorld = () => {
const [state, setState] = useState(initialState);
useEffect(() => {
axios.get(url)
.then(res => {
setState(...state, card: res.data, loading: true);
})
.catch(error => {
setState(error.respone.data);
});
},[]);
return(
{
state.loading && <h1> Welcome to Stackoverflow </h1>
}
);
}
I am not able to write the test case for this sceniarion based on hook

mock api call in a custom hook using react test library

I have written a custom hook and inside it's useEffect function I am calling an API and set the result into state. Here is my custom hook:
export const useGetUsers = (searchParams?: any | undefined, userId?: string) => {
const [users, setUsers] = useState<{
data: readonly any[] | null;
loading: boolean;
}>({
data: [],
loading: true,
});
const parsedSearchParams = {
limit: 100,
...(searchParams || {}),
};
const searchParamStr = `?${makeQueryStringFromObject(parsedSearchParams)}`;
useEffect(() => {
userRequest('users', 'get', null, searchParamStr)
.then(result => {
setUsers({
data: result?.data,
loading: false,
});
})
.catch(() => {
setUsers({
data: null,
loading: false,
});
});
}, [userId, searchParamStr]);
return { users, setUsers };
};
I want my test to get through .then(). but for some reason it does not. here is my test:
test('when the call is a success', async () => {
const spy = jest.spyOn(ES, 'userRequest');
const returnPromise = Promise.resolve({data: ['a']})
ES.userRequest = jest.fn(() => returnPromise);
const { result, waitFor} = renderHook(() => useGetUsers());
await act(() => returnPromise)
await waitFor(() => expect(spy).toHaveBeenCalled())//this fails
});
here is another try and change I made in my test, but no luck:
test('when the call is a success', async () => {
jest.mock('src/..', () => ({
...jest.requireActual('src/..'),
userRequest: jest
.fn()
.mockImplementation(() => new Promise(resolve => resolve({data: ['a']}))),
}));
const { result, waitFor} = renderHook(() => useGetUsers());
await waitFor(() => expect(ES.userRequest).toHaveBeenCalled())
});
P.S. when I mock userRequest, I expect to have the return value as I mocked. but it fails. it goes to .catch instead
I tried to use waitForNextUpdate, but no luck. I would appreciate your help
This works for me:
import { renderHook, waitFor } from '#testing-library/react';
import { useGetUsers } from '../useGetUsers';
import * as utils from '../useGetUsersUtils';
it('should work', async () => {
const mockUserRequest = jest.spyOn(utils, 'userRequest');
renderHook(() => useGetUsers());
await waitFor(() => expect(mockUserRequest).toHaveBeenCalled())
});
I am not sure where is the userRequest placed in your code. As you can see from my import it is in different file then the hook.

Can't perform react state in React

I'm having a problem. been browsing some questions here but seems doesn't work for me.
I'm getting this error in my three pages when I'm using the useEffect.
This is the code of my useEffect
const UserDetailsPage = () => {
const classes = useStyles()
const [userData, setUserData] = useState({
_id: "",
name: "",
phone: "",
address: "",
birthdate: "",
gender: "",
messenger: "",
photo: "",
email: "",
})
const [open, setOpen] = useState(false)
const [loaded, setLoaded] = useState(false)
const { height, width } = useWindowDimensions()
const {
_id,
name,
phone,
address,
photo,
gender,
messenger,
birthdate,
email,
} = userData
useEffect(() => {
const user = getUser()
getUserById("/user/" + user.userId, user.token)
.then((data) => {
setUserData(data)
setLoaded(true)
})
.catch((error) => {
console.log(error)
})
}, [])
Short of getUserById returning a cancel token to cancel any inflight network requests, or an "unsubscribe" method, you can use a React ref to track if the component is still mounted or not, and not enqueue the state update if the component has already unmounted.
const isMountedRef = React.useRef(false);
useEffect(() => {
isMountedRef.current = true;
return () => isMountedRef.current = false;
}, []);
useEffect(() => {
const user = getUser();
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if (isMountedRef.current) {
setUserData(data);
setLoaded(true);
}
})
.catch((error) => {
console.log(error);
});
}, []);
This is because of the async call in useEffect finishing and then attempting to setState after the page is no longer in focus.
It can be avoided by refactoring the useEffect like so:
useEffect(() => {
// created a boolean to check if the component is mounted, name is arbitrary
let mounted = true;
const user = getUser();
getUserById("/user/" + user.userId, user.token)
.then((data) => {
// only setState if mounted === true
if (mounted) {
setUserData(data);
setLoaded(true);
}
})
.catch((error) => {
console.log(error);
});
// set mounted to false on cleanup
return () => {
mounted = false;
};
}, []);
What's different here is that I use a mounted boolean to check if the page is currently mounted. By wrapping the setState call inside an if state, I can check if it's safe to setState, therefore avoiding the error.
Additional reading
This happens when your component is unmounting before setting your state. Try this code below to check if the component is mounted or not.
useEffect(() => {
let isMounted = true; // add a flag to check component is mounted
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if(mounted) { // set state only when component is mounted
setUserData(data)
setLoaded(true)
}
})
.catch((error) => {
console.log(error)
})
return () => { isMounted = false }; // cleanup toggles value, if unmounted
}, []);
Don't use async tasks in useEffect. Define an async function and call in your useEffect.
Example:
const getSTH = async() =>{
getUserById("/user/" + user.userId, user.token)
.then((data) => {
if(mounted) { // set state only when component is mounted
setUserData(data)
setLoaded(true)
}
})
.catch((error) => {
console.log(error)
})
}
useEffect (()=>{
getSTH();
},[])
I think this approach will help you.

react hooks useState consuming object

I am not sure how to make it correctly so I can pass object to useState
const App = () => {
const [weatherData, setWeatherData] = useState({data: "", time: ""});
useEffect(() => {
axios.get(apiUrl).then(response => {
setWeatherData({...weatherData, data: response.data, time: timestamp});
});
}, []);
return <div>{weatherData && <Weather data={weatherData.data} />}</div>;
};
when I do the same just with useState() and setWeatherData(response.data) it works fine but I would like to add the time
Have you tried the following:
setWeatherData({
...response.data,
time: timestamp,
});
P.S. Let me know if I understood you correctly.
UPD
Other option, if you need to access the current state:
useEffect(() => {
axios.get(apiUrl).then(response => {
const timestamp = Date.now().timestamp;
setWeatherData((prevWeatherData) => ({
...prevWeatherData,
data: response.data,
time: timestamp,
}));
});
}, []);
Try this:
const App = () => {
const [weatherData, setWeatherData] = useState(null);
useEffect(() => {
async function fetchWeather () {
const response = await axios.get(apiUrl)
setWeatherData({data: response.data, time: new Date().getTime()});
}
fetchWeather()
}, [weatherData]);
return (
<>
{weatherData && <Weather data={weatherData.data} />}
</>
);
};

Validate unit test logic that runs inside a Promise.resolve

Setup
react : 16.6.0
react-native : 0.57.4
jest : 23.6.0
enzyme : 3.5.0
I have the following logic inside a component
onRefresh = () => {
const { getCustomerAccounts } = this.props
this.setState({ refreshing: true })
getCustomerAccounts()
.then(() => this.setState({ refreshing: false }))
};
which I'm trying to test is using jest like so
describe('Instance', () => {
const getCustomerAccountsMock = jest.fn(() => Promise.resolve({}))
const props = {
getCustomerAccounts: getCustomerAccountsMock,
}
const instance = shallow(<Component {...props} />).instance()
describe('onRefresh', () => {
it('should call getCustomerAccounts', () => {
instance.onRefresh()
expect(getCustomerAccountsMock).toHaveBeenCalled()
expect(getCustomerAccountsMock).toHaveBeenCalledTimes(1)
expect(getCustomerAccountsMock.mock.calls[0][0]).toBeUndefined()
})
})
})
test runs fine but I'm not able to test what happens when getCustomerAccounts().then() runs
Basically I want to test does this.state.refreshing get set to false when getCustomerAccounts().then() runs
Suggestions?
Return the Promise from onRefresh:
onRefresh = () => {
const { getCustomerAccounts } = this.props
this.setState({ refreshing: true })
return getCustomerAccounts() // <= return the Promise
.then(() => this.setState({ refreshing: false }))
};
...then you can test it like this:
describe('Instance', () => {
const getCustomerAccountsMock = jest.fn(() => Promise.resolve({}))
const props = {
getCustomerAccounts: getCustomerAccountsMock,
}
const wrapper = shallow(<Component {...props} />)
const instance = wrapper.instance()
describe('onRefresh', () => {
it('should call getCustomerAccounts', async () => { // <= async test function
await instance.onRefresh() // <= await the Promise
expect(getCustomerAccountsMock).toHaveBeenCalled()
expect(getCustomerAccountsMock).toHaveBeenCalledTimes(1)
expect(getCustomerAccountsMock.mock.calls[0][0]).toBeUndefined()
expect(wrapper.state('refreshing')).toBe(false); // Success!
})
})
})
Details
Returning the Promise lets you await it in the test.
Use an async test function so you can await the returned Promise.
Assign the wrapper to a variable so you can use it to check the state.

Resources