Mock useFetch hook with Jest - reactjs

Im having this functional component with the useFetch hook:
function Foo(){
const { data, isPending, run } = useFetch(`http://localhost:8080/users`);
}
How can I mock the data const here without doing an API call? The useFetch hook is from the library react-async

import React from "react";
import reactAsync from "react-async";
jest.mock('reactAsync');
it("test", async () => {
const data = []
const isPending = false
const run = null
reactAsync.useFetch.mockReturnValueOnce({ data, isPending, run });
});
see here for other ways

Related

Use `useQuery` hook the same way as `useMutation` fails

Using React and Apollo React hooks
import { useCallback, useState } from "react";
import { useMutation, useSubscription, useQuery } from "#apollo/react-hooks";
I got a function useChannel in which I have hooks with all my graphql mutations.
Each mutation is imported and then declared as a constant like so:
const [addExportChannel] = useMutation(AddExportChannelMutation);
Which I consume in the returned function of the hook like so
const onAddImportChannel = useCallback(
async (props) => {
try {
const data = await addImportChannel({
variables: {
shopId,
name: props.name,
url: props.url,
filetype: props.filetype
}
});
...
When I try to do the same with useQuery it fails saying useQuery is not iterable.
TypeError: useQuery is not a function or its return value is not iterable
const [FeedProcess] = useQuery(GetFeedProcessQuery);
...
const onFeedProcess = useCallback(
async (props) => {
try {
const data = await FeedProcess({
variables: { shopId, feedId: props.feedId }
});
...
What am I missing here, trying to figure out what is so different in the two hooks.
Because useQuery is called immediately you cannot store it like this (const [FeedProcess] = useQuery(GetFeedProcessQuery);). But apollo's got useLazyQuery that can be stored like you did. If you'd like to store and invoke it later, use useLazyQuery instead.
You should de-structure the props from useQuery hook like so:
const {data, loading, error} = useQuery(GetFeedProcessQuery);

Create React App - How to mock globally your own component

I have a component that I would like to mock for my test, which exists in src/components/shared/DebouncedInput.js and it looks like this:
import { useState, useCallback } from 'react'
import debounce from 'lodash.debounce'
const useDebounce = (callback, delay) => {
const debouncedFn = useCallback(
debounce((...args) => callback(...args), delay),
[delay] // will recreate if delay changes
);
console.log('debouncedFn', debouncedFn);
return debouncedFn;
};
function DebouncedInput(props) {
const [value, setValue] = useState(props.value);
const debouncedSave = useDebounce(
(nextValue) => props.onChange(nextValue),
props.delay
);
const handleChange = (nextValue) => {
setValue(nextValue);
debouncedSave(nextValue);
};
return props.renderProps({ onInputChange: handleChange, value });
};
export default DebouncedInput;
I also have mocks for third party libraries in src/mocks folder.
Mocks for them work fine in my tests, but I am not sure where to put a mock for my own component DebouncedInput that I would like to mock so that it is globally available to my tests:
import React from "react";
function DebouncedInput(props) {
const handleChange = (nextValue) => props.onChange(nextValue);
return props.renderProps({ onInputChange: handleChange, value: props.value });
};
export default DebouncedInput;
I have put it in the src/components/shared/mocks/DebouncedInput.js folder, but that my test is still hitting the original implementation and not the mock. How should I implement this mock?

what is the equivalent of this.props.history.push('/some_route') of react class components in hooks?

I am trying to accomplish this
import {isAuthorized} from '../somewhere'
async componentDidMount() {
const authorized = await isAuthorized()
if(!authorized){
this.props.history.push('/login')
}
}
in react hooks, how can I achive this exact functionality, thanks
You can use useHistory hook to get access to the history instance and call history.push to inside a useEffect to navigate to the desired route.
import {isAuthorized} from '../somewhere';
import { useHistory } from "react-router-dom";
function SomeComponent(props) {
const history = useHistory()
useEffect(() => {
const navigate = async () => {
const authorized = await isAuthorized();
if(!authorized){
history.push('/login')
}
}
// call the async function
navigate()
}, [])
}
Keep in mind React doesn't allow the callback for useEffect to be an async function because of possible race conditions. So you have to define a new async function inside the useEffect hook and invoke it.
useEffect(() => {
const apiCall = async () => {
const authorized = await isAuthorized()
if(!authorized){
props.history.push('/login');
}
}
apiCall()
},[])
You need to wrap your await in an async function and call that async function apiCall in the body of the useEffect function.
You will need to use useEffect hook:
useEffect(async () => {
const authorized = await isAuthorized()
if(!authorized){
props.history.push('/login') // note: not this.props but simply props
}
},[]) // empty array to call effect in mounted period.
But react doesn't allow to perform async action directly in useEffect hook. You could wrap an async function inside that:
useEffect(() => {
(async () => {
//...
})()
},[])
For further detail, you may take a look into my another post.
I think in this case you could use useHistory hook which is used like this:
import React from 'react'
import { useHistory } from 'react-router-dom'
export default props => {
const history = useHistory()
React.useEffect(() => {
const isLogged = async () => {
const authorized = await isAuthorized()
return authorized
}
const authorized = isLogged()
if(!authorized)
history.replace('/login') // to replace the route , in order to push : history.push('/login')
},[])
}
EDIT:
Yes, it cannot be made async, I changed the answer. Sorry.
There is one more alternative which comes from redux-react-hooks library's useDispatch() hook. It generally is used to call the store actions which in turn call's the store reducer but you can also pass push method defined in react-router-redux for passing in your routes/pathname data.
import {isAuthorized} from '../somewhere';
import {useDispatch} from "redux-react-hook";
import {push} from "react-router-redux";
async componentDidMount() {
const authorized = await isAuthorized();
const dispatch = useDispatch();
if(!authorized){
dispatch(push('/login'));
}
}

TypeError: undefined is not a function when unit-testing custom redux hook

I have a custom hook, that calls a saga function that in turn calls an axios api, when testing this function im getting
TypeError: undefined is not a function
I just want to test if this function got called.
postsHook.test.tsx
import { renderHook } from "#testing-library/react-hooks";
import usePostsHook from "./postsHook";
import { initCommentUpdates, getPostsInit } from "../actions/postActions";
import { getPosts } from "../selectors/selectors";
import { useSelector, useDispatch } from "react-redux";
describe("usePostsHook hook", () => {
const [posts] = renderHook(() => usePostsHook());
expect(posts).toHaveBeenCalledWith(1);
});
postsHooks.tsx
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { initCommentUpdates, getPostsInit } from "../actions/postActions";
import { getPosts } from "../selectors/selectors";
function usePostsHooks() {
const dispatch = useDispatch();
const posts = useSelector(getPosts());
React.useEffect(() => {
dispatch(getPostsInit());
console.log("post hooks got called");
dispatch(initCommentUpdates());
}, []);
return [posts];
}
export default usePostsHooks;
posts is not a function in your hook, but something selected from store and I presume is the return from your API. so expect(posts).toHaveBeenCalledWith(1); is natural since it is not a function.
To test whether your dispatch occurred or not, you need to mock it. Here is an example I use in my tests:
import * as ReactRedux from 'react-redux';
// this mock will be the dispatch function that redux returns on useDispatch()
const mockDispatch = jest.fn();
beforeAll(() => {
// tells useDispatch to return the mocked dispatch
ReactRedux.useDispatch = jest.fn().mockImplementation(() => mockDispatch);
// tells useSelector to return an empty array
ReactRedux.useSelector = jest.fn().mockImplementation(() => []);
});
beforeEach(() => {
// clear the mocks to refresh their calls info
ReactRedux.useDispatch.mockClear();
mockDispatch.mockClear();
});
Later, in your test
expect(posts).toEqual([])
expect(mockDispatch).toHaveBeenCalledWith(getPostsInit())
The thing here is that you are just unit testing your hook, ie: It returns what useSelector returns, it fires some dispatches, without knowing what is the actual real implementation of useSelector or useDispatch

How to write a test for conditional rendering component depended on useState hook in React?

I'm trying to write a test for my functional component, but don't understand how to mock isRoomsLoaded to be true, so I could properly test my UI. How and what do I need to mock?
import React, { useState, useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchRooms } from '../../store/roomsStore'; // Action creator
// Rooms component
export default ({ match, location, history }) => {
const roomsStore = useSelector(state => state.rooms);
const dispatch = useDispatch();
const [isRoomsLoaded, setRoomsLoaded] = useState(false);
useEffect(() => {
const asyncDispatch = async () => {
await dispatch(fetchRooms());
setRoomsLoaded(true); // When data have been fetched -> render UI
};
asyncDispatch();
}, [dispatch]);
return isRoomsLoaded
? <RoomsList /> // Abstraction for UI that I want to test
: <LoadingSpinner />;
};
If you want, you could flat out mock useState to just return true or false, to get whichever result you want by doing the following.
const mockSetState = jest.fn();
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: value => [true, mockSetState],
}));
By doing this, you're effective mocking react, with react, except useState, its a bit hacky but it'll work.

Resources