How to write test cases to cover all the nested 'then' callbacks in a promise chain - reactjs

I'm having difficulty covering the entire promise chain in my unit test coverage. I did find articles that gave me the nearest solution but the challenge is at the last 'then' I need to call three function that does not return a promise.
Below is the example/Sample I tried
async = jest.fn(() => {
return Promise.resolve('value');
});
async1 = jest.fn(() => {
return Promise.resolve('value1');
});
async2 = jest.fn(() => {
return Promise.resolve('Final Value');
});
it('test my scenario', (done) => {
someChainPromisesMethod()
.then(data => {
expect(async1).toBeCalledWith('value');
expect(async2).toBeCalledWith('value1');
expect(data).toEqual('Final Value');
done();
});
});
Below is the function which returns another function with nested 'then' functions. I need help with the test cases to cover them all.
function consolidatedReport(param1, param2){
const somedata = param1.data;
const someOtherData = param2.data;
if(true){
doThisthing();
}
return promiseChainBegin(somedata, someOtherData)
.then(response => response && functionOne(somedata, someOtherData)
.then(response => response && functionTwo(somedata, someOtherData)
.then(response => response && functionThree(somedata, someOtherData)
.then(response => response && functionFour(somedata, someOtherData)
.then(response => {
if(response) {
notApromiseFuncOne(somedata)(someOtherData);
notApromiseFuncTwo(somedata)(someOtherData);
notApromiseFuncThree(somedata)(someOtherData);
} else{
notApromiseFailCase(someOtherData);
}
});
}
I'm having difficulty covering the nested then functions.

You'd mock each of functionOne, etc resolved values:
import functionOne from '../path/to/functionOne';
import functionTwo from '../path/to/functionTwo';
import functionThree from '../path/to/functionThree';
jest.mock('../path/to/functionOne');
jest.mock('../path/to/functionTwo');
jest.mock('../path/to/functionThree');
it('test my scenario', () => {
functionOne.mockResolvedValue('value 1');
functionTwo.mockResolvedValue('value 2');
functionTwo.mockResolvedValue('value 3');
return someChainPromisesMethod()
.then(data => {
expect(functionOne).toBeCalledWith('value returned by promise');
expect(functionTwo).toBeCalledWith('value 1');
expect(functionThree).toBeCalledWith('value 2');
expect(data).toEqual('Final Value');
});
});
This is not exactly your code, but the idea goes like that. You mock the resolved value for each of your functions.

Related

React how to wait for all axios to finish

I want to wait until all axios in useEffect are finished.
UseEffect:
useEffect(() => {
async function getHomePageContent() {
await HomePageServices.getSliderContent().then((response) => {
setSliderProduct(response.data);
});
await HomePageServices.getRecommendedProducts().then((response) => {
setRecommendedProducts(response.data);
});
await HomePageServices.getMostOrderProducts().then((response) => {
setMostOrderProducts(response.data);
});
await HomePageServices.getMostRatedProducts().then((response) => {
setMostRatedProducts(response.data);
});
}
getHomePageContent().catch((error) => {
console.log(error)
});
}, []);
Class:
class HomePageServices{
async getSliderContent(){
return await axios.get(baseURL+"/slider")
}
async getMostRatedProducts(){
return await axios.get(baseURL+"/mostRatedProducts")
}
async getMostOrderProducts(){
return await axios.get(baseURL+"/mostOrderProduct")
}
async getRecommendedProducts(){
return await axios.get(baseURL+"/recommendedProduct")
}
}
Can someone explain to me how to wait for all axios to end, and if one failed, how to find out which one it was?
Try using Promise.allSettled() which takes an iterable (e.g. array) of promises and resolves into array of results of each of them.
Results are represented as objects with status key, which can be rejected or fulfilled. The second key of the object is either value containing the resolved value, or reason in case promise was rejected.
Taking this, then your code in useEffect might be something like this:
useEffect(() => {
const getHomePageContent = async () => ({
const promises = [
HomePageServices.getSliderContent(),
HomePageServices.getRecommendedProducts(),
HomePageServices.getMostOrderProducts(),
HomePageServices.getMostRatedProducts()
];
const data = await Promise.allSettled(promises);
const [slider, recommended, mostordered, mostrated] = data;
// result for each of promise
console.log(slider); // { status: 'fulfilled', value: 123 }
console.log(recommended) // { status: 'rejected', reason: 'blah'}
});
getHomePageContent().catch((er) => console.log(er))
}, [])

Using JS native fetch() api in React ComponentDidMount() results in a pending Promise

I was trying to load data into my project from the public folder in the componentDidMount() lifecycle method. However, I didn't get the desired FeatureCollection Object but a pending Promise.
componentDidMount = () => {
...
const data = fetch(`vcd/${this.state.monthFile}`)
.then(response => response.text())
.then(async data => {
return csv2geojson.csv2geojson(data, {
latfield: 'lat',
lonfield: 'lng',
delimiter: ','
}, (err, data) => {
if (err) console.log(err);
console.log(data); // correctly outputs a FeatureCollection, length 30277
return data;
// this.setState({ someAttribute: data }) => Also doesn't work.
})
})
.then(data => data); // If to use another Promise chaining, the result would be undefined.
console.log(data); // a pending Promise
}
My file contains 30277 rows * 3 columns, ~500Kb in size, which I think shouldn't be a problem with data loading, and after consulting the csv2geojson and fetch API, I still can't think of a solution to this problem. I am grateful for any helpful inputs.
EDIT: Using both async-await pattern and chaining another .then would result in undefined.
JS Fetch returns a promise so its because you're returning that promise.
So just change your code like this it will work;
import React, { useEffect, useState } from "react";
export default function ExampleHooks() {
const [data, setData] = useState(null);
var csv2geojson = require("csv2geojson");
useEffect(() => {
fetch("https://gw3xz.csb.app/sample.csv")
.then((response) => response.text())
.then(async (data) => {
csv2geojson.csv2geojson(
data,
{
latfield: "lat",
lonfield: "lng",
delimiter: ","
},
(err, data) => {
if (err) console.log(err);
setData(data);
}
);
});
}, []);
return <div onClick={() => console.log(data)}>show data</div>;
}
or as a Class Component:
import React from "react";
var csv2geojson = require("csv2geojson");
class ExampleClass extends React.Component {
state = {
data: null
};
componentDidMount() {
fetch(`vcd/${this.state.monthFile}`)
.then((response) => response.text())
.then(async (data) => {
csv2geojson.csv2geojson(
data,
{
latfield: "lat",
lonfield: "lng",
delimiter: ","
},
(err, data) => {
if (err) console.log(err);
this.setState({ data: data });
}
);
});
}
render() {
return <div onClick={() => console.log(this.state.data)}>show data</div>;
}
}
export default ExampleClass;
Working example over here
fetch returns a promise, and that is what you save to data. If you want to log the "data", then you have a couple options.
Log it IN the promise chain (you already do that)
Convert over to async/await and await the fetch to resolve/reject
code
componentDidMount = async () => {
...
const data = await fetch(`vcd/${this.state.monthFile}`)
.then(response => response.text())
.then(data => {
return csv2geojson.csv2geojson(data, {
latfield: 'lat',
lonfield: 'lng',
delimiter: ','
}, (err, data) => {
if (err) console.log(err);
console.log(data);
return data;
})
});
console.log(data); // a resolved/rejected Promise result
}

Jest mock module resolve with variable value

Assuming I have a module which returns a promise.
I want to mock different outcomes of this promise to test the function where this module is part of. I mock the module like this:
jest.mock('../request', () => {
return () => new Promise((resolve, reject) => {
return resolve({
response: { ok: true }
});
});
});
My first test is running
test("The function resolves", () => {
const initialState = { apiData: getState("postData", {}, "ready", "POST") };
const store: any = mockStore(initialState);
return expect(
performApiRequest("postData/", {}, { data: "json" })(dispatch, () =>
store.getState()
)
).resolves.toBeUndefined();
});
The problem is now with testing an other function where the value that resolves is supposed to be different, for instance {response: { ok: false } }.
I already tried different things. First wrapping the mock in a function and give the response as an argument. --> fails for mocks can't take out of scope variables.
I tried to call jest.doMock within the test but this does not trigger the request correctly.
I tried to do
const mockResponse = jest.fn();
jest.mock("../request", () => {
return () =>
new Promise((resolve, reject) => {
return resolve({
mockResponse
});
});
});
And then call mockResponse.mockReturnValueOnce(value).
No success yet. Anybody sees a way out?
You can create a default mock function at the top level with jest.fn. Once you create the mock you can override the implementation of the function within the test case with mockImplementation or mockImplementationOnce. You can find more information about this in the Jest documentation.
import request from '../request';
jest.mock("../request", () =>
jest.fn(() =>
Promise.resolve({
response: {
ok: true
}
})
)
);
test("MyTest", () => {
request.mockImplementationOnce(() =>
Promise.resolve({
response: {
ok: false
}
})
);
});
answer with typescript would be:
import request from '../request';
jest.mock("../request", () =>
jest.fn(() =>
Promise.resolve({
response: {
ok: true
}
})
)
);
test("MyTest", () => {
(request as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
response: {
ok: true
}
})
);
});

How do I properly test for a rejected promise using Jest?

Code
import { createUser } from '../services';
...
...
handleFormSubmit = () => {
this.setState({ loading: true });
createUser()
.then(() => {
this.setState({
loading: false,
});
})
.catch(e => {
this.setState({
error: e,
});
});
};
Test
it('rejects...', () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
return wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
});
});
Mock
export const createUser = function() {
return new Promise((resolve, reject) => {
reject('error');
});
};
The test does force the code to go into the catch in the method. So the state does get set to 'error'.
But in my test, it doesn't do what I expect and wait for the Promise to reject before it tests for the state change.
I'm not sure what to try here, should I be using async/await?
So it's the createUser method I want to wait for but I'm not sure my implementation allows for this.
You should do something like this:
it('rejects...', () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
return expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});
I think it is cleaner this way. You can see this approach in the official docs.
It's important to note that .rejects (and .resolves) returns a promise, which is returned in the example above so that jest knows to wait on it. If you don't return it, you MUST await it:
it('rejects...', async () => {
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
await expect(wrapper.instance().handleFormSubmit()).rejects.toEqual('error');
});
The test fails because it's not aware that the subject is asynchronous. It can be fixed by using a done param or making the test function async.
Note it's also necessary to set the number of expected assertions so that the test will fail even if the catch branch is not taken.
async/await style:
it('rejects...', async () => {
expect.assertions(1);
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
await wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
});
});
Older style done param:
it('rejects...', done => {
expect.assertions(1);
const Container = createUserContainer(CreateUser);
const wrapper = shallow(<Container />);
wrapper.instance().handleFormSubmit()
.catch(e => {
console.log("State: ", wrapper.state());
expect(e).toEqual('error');
done();
});
});
Asynchronous Testing Reference
expect.assertions reference
Your code looks correct. Why do you say that it doesn't wait for the Promise to reject? The only difference I would make would be to make use of Jest's mocking capability, so change
Mock
export const createUser = function() {
return new Promise((resolve, reject) => {
reject('error');
});
};
to
Test
jest.mock('../services');
const services = require('../services');
const createUser = jest.spyOn(services, "createUser");
createUser.mockRejectedValue("error");
...
it('rejects...', () => {
There's no need to have a separate Mock file
In your code handleFormSubmit function should return Promise on which you can wait in your test. Also you need to return truthful data from success and error callback to resolve and reject the promise respectively.
handleFormSubmit = () => {
this.setState({ loading: true });
return createUser()
.then(() => {
this.setState({
loading: false,
});
return true;
})
.catch(e => {
this.setState({
error: e,
});
throw e;
});
};
Here in your actual code you have caught the error in catch handler and trying to catch it further in out test case code. Hence catch can not be chained further, while you can chain then multiple times.
For reference go through Promise documentations:
https://www.peterbe.com/plog/chainable-catches-in-a-promise

How to unit test Promise catch() method behavior with async/await in Jest?

Say I have this simple React component:
class Greeting extends React.Component {
constructor() {
fetch("https://api.domain.com/getName")
.then((response) => {
return response.text();
})
.then((name) => {
this.setState({
name: name
});
})
.catch(() => {
this.setState({
name: "<unknown>"
});
});
}
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
Given the answers below and bit more of research on the subject, I've come up with this final solution to test the resolve() case:
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve({
text: () => Promise.resolve("John Doe")
});
global.fetch = () => fetchPromise;
const app = await shallow(<Application />);
expect(app.state("name")).toEqual("John Doe");
});
Which is working fine. My problem is now testing the catch() case. The following didn't work as I expected it to work:
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.reject(undefined);
global.fetch = () => fetchPromise;
const app = await shallow(<Application />);
expect(app.state("name")).toEqual("<unknown>");
});
The assertion fails, name is empty:
expect(received).toEqual(expected)
Expected value to equal:
"<unknown>"
Received:
""
at tests/components/Application.spec.tsx:51:53
at process._tickCallback (internal/process/next_tick.js:103:7)
What am I missing?
The line
const app = await shallow(<Application />);
is not correct in both tests. This would imply that shallow is returning a promise, which it does not. Thus, you are not really waiting for the promise chain in your constructor to resolve as you desire. First, move the fetch request to componentDidMount, where the React docs recommend triggering network requests, like so:
import React from 'react'
class Greeting extends React.Component {
constructor() {
super()
this.state = {
name: '',
}
}
componentDidMount() {
return fetch('https://api.domain.com/getName')
.then((response) => {
return response.text()
})
.then((name) => {
this.setState({
name,
})
})
.catch(() => {
this.setState({
name: '<unknown>',
})
})
}
render() {
return <h1>Hello, {this.state.name}</h1>
}
}
export default Greeting
Now we can test it by calling componentDidMount directly. Since ComponentDidMount is returning the promise, await will wait for the promise chain to resolve.
import Greeting from '../greeting'
import React from 'react'
import { shallow } from 'enzyme'
test("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve({
text: () => Promise.resolve('John Doe'),
})
global.fetch = () => fetchPromise
const app = shallow(<Greeting />)
await app.instance().componentDidMount()
expect(app.state('name')).toEqual('John Doe')
})
test("greeting name is '<unknown>'", async () => {
const fetchPromise = Promise.reject(undefined)
global.fetch = () => fetchPromise
const app = shallow(<Greeting />)
await app.instance().componentDidMount()
expect(app.state('name')).toEqual('<unknown>')
})
By the looks of this snippet
.then((response) => {
return response.text();
})
.then((name) => {
this.setState({
name: name
});
})
it seems that text would return a string, which then would appear as the name argument on the next 'then' block. Or does it return a promise itself?
Have you looked into jest's spyOn feature? That would help you to mock not only the fetch part but also assert that the setState method was called the correct amount of times and with the expected values.
Finally, I think React discourages making side effects inside constructor. The constructor should be used to set initial state and other variables perhaps. componentWillMount should be the way to go :)
Recently, I have faced the same issue and ended up resolving it by following way
(taking your code as an example)
test.only("greeting name is 'John Doe'", async () => {
const fetchPromise = Promise.resolve(undefined);
jest.spyOn(global, 'fetch').mockRejectedValueOnce(fetchPromise)
const app = await shallow(<Application />);
await fetchPromise;
expect(app.state("name")).toEqual("<unknown>");});
Another way if you don't want to call done then return the next promise state to jest. Based on result of assertion( expect ) test case will fail or pass.
e.g
describe("Greeting", () => {
test("greeting name is unknown", () => {
global.fetch = () => {
return new Promise((resolve, reject) => {
process.nextTick(() => reject());
});
};
let app = shallow(<Application />);
return global.fetch.catch(() => {
console.log(app.state());
expect(app.state('name')).toBe('<unknown>');
})
});
});

Resources