How can I test useEffect with async function in Jest? - reactjs

I have this function inside a helper:
export const useDAMProductImages = (imageId: string) => {
const {
app: { baseImgDomain },
} = getConfig();
const response: MutableRefObject<string[]> = useRef([]);
useEffect(() => {
const getProductImages = async (imageId: string) => {
try {
const url = new URL(FETCH_URL);
const res = await fetchJsonp(url.href, {
jsonpCallbackFunction: 'callback',
});
const jsonData = await res.json();
response.current = jsonData;
} catch (error) {
response.current = ['error'];
}
};
if (imageId) {
getProductImages(imageId);
}
}, [imageId]);
return response.current;
};
In test file:
import .....
jest.mock('fetch-jsonp', () =>
jest.fn().mockImplementation(() =>
Promise.resolve({
status: 200,
json: () => Promise.resolve({ set: { a: 'b' } }),
}),
),
);
describe('useDAMProductImages', () => {
beforeEach(() => {
jest.clearAllMocks();
cleanup();
});
it('should return empty array', async () => {
const { result: hook } = renderHook(() => useDAMProductImages('a'), {});
expect(hook.current).toMatchObject({ set: { a: 'b' } });
});
});
The problem is that hook.current is an empty array. Seems that useEffect is never called. Can someone explain to me what I'm doing wrong and how I should write the test? Thank you in advance

Related

Don't work deleting coin because old value in setInterval method react hooks

I want delete value in removeCurrency method. The value is being deleted but after 2-3 seconds value is returned. This is hapenning when fetchData method is start. In method fetchData value cryptoCurrency is old.
export function Crypto(props: CryptoProps){
const [cryptoCurriensies, setCryptoCurriensies] = useState<Coin[]>([ {usd: null, name: 'LUNA'}, {usd: null, name: 'BNB'}, {usd: null, name: 'XRP'}]);
const [error, setError] = useState('');
const [intervalId, setIntervalId] = useState<number| undefined>(undefined)
async function removeCurrency(cryptoName: string){
let coins = [...cryptoCurriensies];
let filteredCoins = coins.filter(coin => coin.name !== cryptoName)
setCryptoCurriensies(filteredCoins);
}
useEffect(() => {
fetchData();
let intervalId = window.setInterval(() => fetchData(), 5000);
return () => {
window.clearInterval(intervalId);
}
}
, [cryptoCurriensies]);
async function fetchData() {
try {
const coins = await Promise.all(
cryptoCurriensies.map(async (coin) => {
const res = await fetch(`https://min-api.cryptocompare.com/data/price?fsym=${coin.name}&tsyms=USD&api_key=${API_KEY}`);
const usd = await res.json();
let price = Number(usd.USD);
return { ...coin, usd: price };
})
);
setCryptoCurriensies([...coins])
} catch (e) {
console.log(e);
}
}
function setCrypto(coins: Coin[]){
setCryptoCurriensies(coins);
}
const add = (name: string) => {
fetch(`https://min-api.cryptocompare.com/data/price?fsym=${name}&tsyms=USD&api_key=${API_KEY}`, { mode: 'cors' })
.then(res => res.json())
.then(
(result) => {
if (result.Response === 'Error') {
addError(name, result.Message);
return;
}
addNameToState(name);
},
(error) => {
addError(name, error);
}
)
}
function addNameToState(name: string) {
let coins = [...cryptoCurriensies, {name: name, usd: null}];
setCryptoCurriensies(coins);
}
function addError(name: string, error: string) {
setError(`Error occurred: not found crypto with name = ${name} in base ` + error)
}
function clearMessage(){
setError('');
}
return <div className="crypto">
<h1 className="header">CryptoMoney</h1>
<CryptoList cryptoCurrensies={cryptoCurriensies} removeCurrency={removeCurrency} setCoins={setCrypto}/>
<CryptoInput add={add} />
<Modal message={error} title={'Error'} clearMessage={clearMessage} />
</div>
}
}```

Jest Mocked data not loaded during test

I have a component that loads data from an API which I mocked for my test but it is not loaded as the test cannot find the element which contain the data.
component:
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect, useCallback } from "react";
import { businessDataActions } from "../store/business-data";
import { fetchBusinessListing } from "../services/business-listing";
import styles from "../styles/BizCard.module.css";
import BizCardItem from "./BizCardItem";
const BizCard = (props) => {
const dispatch = useDispatch();
const [listing, setListing] = useState([]);
//load all listing
const fetchListing = useCallback(async () => {
dispatch(businessDataActions.setIsLoading({ isLoading: true }));
const ListingService = await fetchBusinessListing();
if (ListingService.success) {
setListing(ListingService.data);
} else {
dispatch(
businessDataActions.setNotify({
severity: "error",
message: "Problem when fetching listing.",
state: true,
})
);
}
dispatch(businessDataActions.setIsLoading({ isLoading: false }));
}, []);
useEffect(() => {
fetchListing();
}, []);
const businessList = listing.map((item) => (
<BizCardItem
key={item.key}
id={item.id}
name={item.name}
shortDescription={item.shortDescription}
imageUrl={item.imageUrl}
/>
));
return (
<div className={styles.grid} role="grid">
{businessList}
</div>
);
};
test file:
const bizListing = [
...some fake data
];
jest.mock("../../services/business-listing", () => {
return function fakeListing() {
return { success: true, data: bizListing };
}
});
afterEach(cleanup);
describe('BizCard', () => {
test("loading listing", async () => {
useSession.mockReturnValueOnce([null, false]);
await act(async () => {render(
<BizCard />
)});
const itemGrid = await screen.findAllByRole("gridcell");
expect(itemGrid).not.toHaveLength(0);
});
});
services/business-listing:
export const fetchBusinessListing = async() => {
try {
const response = await fetch(
"/api/business"
);
if (!response.ok) {
throw new Error('Something went wrong!');
}
const data = await response.json();
const loadedBusiness = [];
for (const key in data) {
let imgUrl =
data[key].imageUrl !== "undefined" && data[key].imageUrl !== ""
? data[key].imageUrl
: '/no-image.png';
loadedBusiness.push({
key: data[key]._id,
id: data[key]._id,
name: data[key].businessName,
shortDescription: data[key].shortDescription,
imageUrl: imgUrl,
});
}
return { success: true, data: loadedBusiness };
} catch (error) {
return ({success: false, message: error.message});
}
}
The test executed with these returned:
TypeError: (0 , _businessListing.fetchBusinessListing) is not a function
48 | // }
49 |
> 50 | const ListingService = await fetchBusinessListing();
Unable to find role="gridcell"
I can confirm gridcell is rendered when I am using browser.
Can anyone please shed some light on my problem
Manage to solve the problem myself, problem is with the mock:
jest.mock("../../services/business-listing", () => {
return {
fetchBusinessListing: jest.fn(() => { return { success: true, data: bizListing }}),
}
});

How to wait for all useEffect Chain using Jest and Enzyme when all of them make asynchronous calls?

I have the following Minimal Component
import React, { useState, useEffect } from "react";
import { API } from "aws-amplify";
export default function TestComponent(props) {
const [appointmentId, setAppointmentId] = useState(props.appointmentId);
const [doctorId, setDoctorId] = useState(null);
useEffect(() => {
const loadDoctor = async () => {
if (doctorId) {
const doctorData = await API.post("backend", "/doctor/get", {
body: {
doctorId: doctorId
}
});
console.log("This does not come", doctorData);
}
}
loadDoctor();
}, [doctorId])
useEffect(() => {
const loadAppointment = async () => {
if (appointmentId) {
const appointmentData = await API.post("backend", "/appointment/get", {
body: {
appointmentId: appointmentId
}
});
console.log("This Loads", appointmentData);
setDoctorId(appointmentData.doctorId);
}
}
loadAppointment();
}, [appointmentId])
return (
<div>Testing Page</div>
)
}
The Following this does not work not load wait for the doctorId useEffect promise.
But the second test this does work waits for both the useEffect
import React from "react";
import { API } from "aws-amplify";
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import TestComponent from "./TestComponent.js";
jest.mock("aws-amplify");
beforeEach(() => {
API.post.mockImplementation((api, path, data) => {
if (path === "/appointment/get") {
return Promise.resolve({
doctorId: "10000001"
});
}
if (path === "/doctor/get") {
return Promise.resolve({
doctorName: "Mr. Doctor"
});
}
});
afterEach(() => {
API.post.mockClear();
});
it("this does not work", async () => {
const wrapper = mount(
<TestComponent appointmentId={"2000001"}/>
);
await act(async () => {
await Promise.resolve(wrapper);
wrapper.update();
});
// this does not print the line console.log("This does not come", doctorData);
});
it("this does work", async () => {
const wrapper = mount(
<TestComponent appointmentId={"2000001"}/>
);
await act(async () => {
await Promise.resolve(wrapper);
wrapper.update();
await act(async () => {
await Promise.resolve(wrapper);
wrapper.update();
});
});
// this prints it. This works, but this is not scalable for more complicated code
});
Is there a way I can wait for all the subsequent useEffect and then test ?
I did something similar to preload images. this put all the promisse in a stack and wait for all to resolve
preloadImages(srcs) {
function loadImage(src) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve(img);
};
img.onerror = img.onabort = function() {
reject(src);
};
img.src = src;
});
}
var promises = [];
for (var i = 0; i < srcs.length; i++) {
//const imgName = srcs[i].substring(15,18)
//promises.push(loadImage(srcs[i]));
promises.push(loadImage(images(`./${srcs[i]}.png`).default));
}
return Promise.all(promises);
}
then.. prelaodImages(...).then(...

Separate functions which depend on each other

I am trying to clean up my code an separate into functions that only have one task.
In v1 joinDailyCo(url); was defined inside fetchUrl(). Now I tried to move it out with
const url = fetchUrl();
joinDailyCo(url);
However, as soon I do that, I get the error message:
Unhandled Rejection (TypeError): Cannot read property 'join' of
undefined
const Daily = ({ eventSlug, tableId }) => {
const classes = useStyles();
const iframeRef = useRef();
const dailyRef = useRef();
const joinedRef = useRef();
useEffect(() => {
// Join call
const joinDailyCo = async (url) => {
if (joinedRef.current) {
// This is needed due to it never returning if there wasn't a meeting joined first.
await dailyRef.current.leave();
}
await dailyRef.current.join({ url });
};
// Retrieve dailySessionId and meetingToken.
const fetchUrl = async () => {
try {
const {
data: { dailySessionId, meetingToken },
} = await api.get(
`events/${eventSlug}/space/tables/${tableId}/daily-auth/`
);
const url = `${DAILY_URL}/${dailySessionId}?t=${meetingToken}`;
return url;
// joinDailyCo(url);
} catch (error) {
Sentry.captureException(error);
}
};
const url = fetchUrl();
url && joinDailyCo(url);
}, [eventSlug, tableId]);
useEffect(() => {
dailyRef.current = DailyIframe.wrap(iframeRef.current, {
// showLeaveButton: true,
});
dailyRef.current.on(eventTypes.LEFT_MEETING, () => {
joinedRef.current = false;
});
dailyRef.current.on(eventTypes.JONING_MEETING, () => {
joinedRef.current = true;
});
return () => {
dailyRef.current.destroy();
};
}, []);
return (
<iframe
ref={iframeRef}
className={classes.root}
title="Video Meeting"
allow="camera; microphone; display-capture; fullscreen"
/>
);
};
export default Daily;

test mapDispatchToProps async actions

I am trying to test my mapDispatchToProps function when an asyncronous function is dispatched. I have read Dan Abramov's suggestions on how to test mapDispatchToProps and I am trying to test my code as such.
I am getting the error...
TypeError: Cannot read property 'then' of undefined
Here is my test...
describe("mapDispatchToProps", () => {
const dispatchSpy = jest.fn();
const {signupUser} = mapDispatchToProps(dispatchSpy);
it("should dispatch signupActions.signupUser()", () => {
// mockAxios.onPost(endpoints.SIGNUP,{})
// .reply(200,'test_success');
// tried with and without mockAxios
signupUser({})
const spyLastCall = dispatchSpy.mock.calls[0][0];
expect(spyLastCall)
.toEqual(signupActions.signupUser({}));
})
})
The function that I want to test...
export const mapDispatchToProps = dispatch => {
return { signupUser: (user) => {
dispatch(signupActions.signupUser(user))
.then((response) => {
// do something on success
}, (error) => {
// do something on failure
})
}
}
I have already tested signupActions.signupUser and I know that it returns a promise. Here is the code...
export function signupUser(user) {
return (dispatch) => {
return dispatch(rest.post(SIGNUP,user))
.then((response) => {
return Promise.resolve(response);
},(error) => {
return Promise.reject(error)
}
)
}}
What am I doing wrong?
Ps: I also tried:
const dispatchSpy = jest.fn().mockImplementation( () => {
return p = new Promise((reject,resolve) => {
resolve({})
})
}
with the same result
For anyone who is interested, I ended up using mergeProps which has made my tests a lot cleaner. Now I have...
export const mapDispatchToProps = dispatch => {
return { dispatchSignupUser: (user) => {
dispatch(signupActions.signupUser(user))
}
}
export const mergeProps = (propsFromState,propsFromDispatch,ownProps) => {
return {
signupUser: (values) => {
return propsFromDispatch.dispatchSignupUser(values)
.then(() => { // do something on success },
() => { // do something on failure})
}
}
and I test them separately...
describe("signup", () => {
/// ... ownProps and propsFromState declared here
const dispatchSpy = jest.fn((x) => {});
const {
dispatchSignupUser,
} = mapDispatchToProps(dispatchSpy);
const signupUser = mergeProps(propsFromState,propsFromDispatch,ownProps);
describe("mapDispatchToProps", () => {
it("should dispatch signup user on dispatchSignupUser", () => {
const spyOn = jest.spyOn(signupActions,'signupUser');
dispatchSignupUser({test: "test"});
expect(spyOn).toHaveBeenCalledWith({test: "test"});
})
})
describe("mergeProps", () => {
it("should do something on success", () => {
propsFromDispatch.dispatchSignupUser jest.fn().mockImplementation((x) => {
return new Promise((resolve,reject) => { return resolve({})} )
});
return signupUser({}).then(() => {
expect(history.location.pathname).toEqual("/signup/thank-you")
}, (error) => {})
})
})
})
Hopefully this is helpful!

Resources