mocking my fetch function does not work, keeps getting back undefined - reactjs

I am trying to mock a simple function that uses fetch. The function in question looks like this:
export const getPokemon = async () => {
//function that makes the API call and fetches our pokemon
//getPokemon.js
const randomId = () => Math.floor(Math.random() * 151 + 1);
const pokemonApiUrl = `https://pokeapi.co/api/v2/pokemon/`;
export const getPokemon = async () => {
//function that makes the API call and fetches our pokemon
const id = randomId();
let pokemon = { name: "", image: "" };
try {
const result = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
console.log(result)
const data = await result.json();
pokemon.name = data.name;
pokemon.image = data.sprites.other["official-artwork"].front_default;
return pokemon;
} catch (err) {
console.error(err);
Whenever I try to mock the function in my unit tests I receive back a TypeError: Cannot read property 'json' of undefined. Basically, the result comes back as undefined and thus we cannot call our .json(). It works fine in production and the fetch calls work as expected. I am using React Testing Library and Jest.
I have tried to replaced the global fetch in the following manner:
//PokemonPage.test.js
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ data: { name: 'Charizard' } }),
})
);
I've also tried to create a fakeFetch and send it in to my function as a dependency injection, but I get the exact same error.
Lastly, I've tried to install jest-fetch-mock but yet again I am getting the same error... Has anyone encountered the same thing?
The failing function gets called in production here:
function Pokemon({ pokemonTrainer }) {
...
useEffect(() => {
async function fetchData() {
pokemonRef.current = await getPokemon();
setPokemonList((prev) => [
...prev,
{ name: pokemonRef.current.name, image: pokemonRef.current.image },
]);
}
fetchData();
}, []);
...
}

Related

Parse data through API with useEffect for Algolia

I need to parse stock data through YahooFinance Stocks API, using RapidAPI here how it would look like as an example response.
https://rapidapi.com/integraatio/api/yahoofinance-stocks1/
The error I am getting is:
Uncaught (in promise) ReferenceError: Cannot access 'data' before
initialization
"results":1294 items
[100 items
0:{4 items
"exchangeCode":"NMS"
"symbol":"1"
"companyName":"1"
"industryOrCategory":"N/A"
}
1:{...}4 items
2:{4 items
"exchangeCode":"NMS"
"symbol":"AAON"
"companyName":"AAON, Inc."
"industryOrCategory":"Industrials"
}
3:{4 items
"exchangeCode":"NMS"
"symbol":"AAPL"
"companyName":"Apple Inc."
"industryOrCategory":"Technology"
}
]
useEffect(() => {
const config = {
headers: {
"x-rapidapi-host": "stock-market-data.p.rapidapi.com",
"x-rapidapi-key": APIKEY,
},
};
const fetchStocks = async () => {
const json = JSON.parse(data);
const results = Object.keys(json["results"]);
const stockInfo = results.map(
(result) =>
(result = {
result,
close: String(json["results"][result]),
})
);
const { data } = await axios.get(
"https://stock-market-data.p.rapidapi.com/market/exchange/nasdaq",
config
);
data.forEach((results) => {
results.objectID = results.length;
});
setStocks(data);
};
fetchStocks();
}, [])
This seems like a marshaling issue in your Javascript more than an Algolia issue. Perhaps because the await axios.get() needs to be in an async function per https://stackabuse.com/making-asynchronous-http-requests-in-javascript-with-axios/
To use the async/await syntax, we need to wrap the axios.get() function call within an async function
Curious the contents of data before and after the data.forEach() (also could that be a map?)

Async/await not working in a for-of loop defined in createAsyncThunk

I'm having trouble trying to get an async await to work inside a for loop when using createAsyncThunk. I expected that dispatch(performRunAllCells()) will call the API updateBrowser() synchronously for each cell in the editor.cells array in order. Instead, the dispatch resulted in updateBrowser() being called asynchronously all at once. What is happening here?
export const performRunAllCells = createAsyncThunk(
'editor/runAllCells',
async (_, { dispatch, getState, rejectWithValue }) => {
const { notebook: { selectedDataset } } = getState() as {notebook: {selectedDataset: string}};
const { editor } = getState() as {editor: EditorState};
try {
let results: DataEntity | undefined;
for (const cell of editor.cells) {
dispatch(setCellStatus({ id: cell.id, execStatus: '*' }));
results = await updateBrowser(selectedDataset, cell.editorContent);
dispatch(setCellStatus({ id: cell.id }));
}
return results;
} catch (e) {
return rejectWithValue(e.response.data);
}
},
);
Edit
Currently I'm testing updateBrowser() with a setTimeout:
export async function updateBrowser(selectedDataset: string, editorContent: string): Promise<DataEntity> {
return new Promise((resolve) => {
setTimeout(() => {
console.log('test');
resolve({
raw: editorContent, html: 'Test', console: 'Test',
});
}, 3000);
});
}
I was able to know if it's synchronous/asynchronous through the console log above. Currently, it is printing multiple "test" at once.
Nevermind. I made a mistake somewhere else and the code wasn't actually being called. It is working now after fixing it.

Testing a promise inside an async function

I'm trying to write a jest test to test a promise that runs within a async function. I'm having a hard time finding resources that describe how to do that.
Below is what I have:
const foo = async (client) => {
const update = data => {};
await client.query({
query: QUERY,
variables: {
x: xVal,
y: yVal,
},
}).then(response => {
update({ response });
}).catch(error => {
update({ [] });
});
}
...
fetch: () => {
foo(client)
},
Essentially I need to test fetch() and then confirm that update() ran in both the success and error case within foo().

Undefined data (sometimes data)

I'm trying to read an Array inside my FireStore document. I want to render the items inside this Array in a component through using .map().
Sometimes, I get a TypeError: Cannot read property 'map' of undefined error. What could be causing it and how can I ensure that it doesn't happen.
interface Product {
summary: string;
details: string;
product: string;
benefit: Array<string>;
}
function ProductInfo({ product }: { product: Product }) {
console.log("Product:",product.summary);
product.benefit.forEach((item) => { //triggers exception sometimes
console.log(item)
})
}
In a different component, this is how I populate the data and pass it to the component above:
function ProductDetails({ match }: RouteComponentProps<TParams>) {
const [product, setProduct]: any = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await db.collection("Products").doc(match.params.id).get();
console.log('response', response.data());
let data: any = { title: 'not found' };
if (response.exists) {
data = response.data();
}
setProduct(data);
} catch (err) {
console.error(err);
}
};
fetchData();
}, []);
return (
<div>
<ProductInfo product={product} />
</div>
)
}
I'm learning React with TypeScript so I'm still trying to get the hang of things. I'm confused as to why it works sometimes and does not at other instances. product.summary gets rendered all the time though. Thank you
EDIT
From debugging, I think I seem to have found the issue:
If I test with:
const [product, setProduct]: any = useState();
useEffect(() => {
const fetchData = async () => {
try {
const response = await db.collection("Products").doc("flex-crm").get();
console.log('response', response.data());
let data: any = { title: 'not found' };
if (response.exists) {
data = response.data();
}
setProduct(data);
} catch (err) {
console.error(err);
}
};
fetchData();
}, []);
console.log("Data: ", product)
I get 3 lines of output in the console (instead of 2). I get
Data: undefined
response {...}
Data: {...}
My prop is using the Data:undefined instance when the component is rendered. How can I update it to use the fetched data?
Try moving setProduct(data); inside the if condition. Maybe for some products response is not present and you are still setting data which will be undefined in that case.
if (response.exists) {
data = response.data();
setProduct(data);
}
Try to use product?.summary.map(...).
Probably this will help you.
~Also why did you use response.data() i could not get it, shouldn't it be response.data?

Jest testing service call responses witth promises, useEffect and useState hooks

I'm having some difficulty testing this useEffect in jest. The following piece of code is within a react functional component and I want to return some mock values when the serviceFn is called. The returned data is written back to state.
//from service-factory.js
const serviceFn = () => (
({ personId }) => (
ionXHR.request(
`/persons/${personId}/`,
'GET',
null,
'json',
)
)
);
//from Component.jsx
const service = useRef(serviceFn());
useEffect(() => {
service.current({ personId:123456 }).then((response) => {
if (response.data) {
setData(response.data);
setLoadingState('SUCCESS');
} else {
setLoadingState('FAILED');
}
});
}, [personId]);
I have the following, but not sure what else I would need.
function mockReturnFn() { return 'Test'; }
const wrapper = mount(<Component/>);
const somethingSpy = jest.spyOn(wrapper, 'serviceFn').mockImplementation(mockReturnFn);
Update:
So, I think I'm getting close.
In my test file I had to import the function
import { serviceFn } from './service-factory';
jest.mock('./service-factory', () => ({ serviceFn : jest.fn() }));
In my test I have
serviceFn.mockImplementation(() => Promise.resolve('test1234'));
The issue now with this is I am getting service.current is not a function
I tried to do this but now getting _serviceFactory.serviceFn.mockImplementation is not a function
jest.mock('./service-factory', () => (
{
serviceFn: {
current: jest.fn(),
},
}
));
serviceFn is factory function, it returns a function that makes requests.
Considering it's named export, it should be initially stubbed as:
jest.mock('./service-factory', () => {
const mockService = jest.fn();
return {
__esModule: true,
mockService,
serviceFn: jest.fn().mockReturnValue(mockService)
}
});
mockService is exposed and allows to mock specific responses:
mockService.mockResolvedValue({ data: ... });
Since it's basic wrapper over ionXHR, it's also possible to mock responses one level higher on ionXHR.request calls.

Resources