I cannot write a test that covers Promise.all() statement within a asynchronous function (loadMessages()) that is ran in setTimeout() block of componentDidMount method.
In componentDidMount there is this.loadMessages() function that is called within setTimeout callback, in order for me to complete my test i need loadMessages() executed.
componentDidMount() {
const { conversationId } = this.state
const POLLING_INTERVAL = 3000
if (conversationId) {
setTimeout(() => this.loadMessages(), 0)
this.timer = setInterval(() => this.loadMessages(), POLLING_INTERVAL)
} else {
this.setState({ loading: false })
}
}
I resolved setTimeout callback with
await new Promise(resolve =>
setTimeout(() => {
resolve()
}, 3000)
)
and that solves a function call, but when start executing a function the report coverage is saying that Promise.all is not covered and function itself looks like:
async loadMessages() {
const { messages, conversationId, errors } = this.state
let messagesWithAuthors
// initial load
if (messages.length === 0) {
try {
let initialMessages = await runtime.dais.communication.auto.getMessagesByConversationId(
conversationId
)
const messageAuthors = await Promise.all(
initialMessages.map(async message =>
//runtime.dais.orgService.auto.getPersonById(message.sender.id)
runtime.dais.organization.auto.getPersonById(message.sender.id)
)
)
messagesWithAuthors = initialMessages.map((message, i) => ({
...message,
author: messageAuthors[i],
}))
this.setState({
messages: messagesWithAuthors,
messageAuthors,
loading: false,
})
} catch (error) {
this.setState({ errors: [...errors, error], hasErrors: true, modalOpen: true })
}
} else {
let updatedMessages = await runtime.dais.communication.auto.getMessagesByConversationId(
conversationId
)
this.checkIfNeedUpdate(updatedMessages)
}
}
is there some way to mock a values that are returned from Promise.all() into messageAuthors variable?
I am testing using #testing-library/react and my test looks like this
it('ensure that we have chat window shown if we have conversation as a prop', async () => {
const queries = render(
<CommWidget runtime={runtime} conversationId="fe3d52fc-ffb3-482a-aedf-79000645ca70" />
)
await new Promise(resolve =>
setTimeout(() => {
resolve()
}, 3000)
)
const commWidget = queries.container.querySelector(
'.ui-comm-widget .ui.segments.comm-widget #chat-window'
)
expect(commWidget).toBeInstanceOf(HTMLDivElement)
})
Please don't put a timeout in your tests that's an anti-pattern. What happens after the promise resolves? Is the page going to change? If so wait for the change to appear. See this article for an introduction on testing async methods.
Related
I'm having an issue getting test coverage for a react action dispatch with window.setTimeout() the test passes with done() but it doesn't actually provide any coverage for the istanbul coverage. I tried a few things but nothing has worked so far. Anyone familiar with testing this? I've also tried using lolex instead to mock the time instead of using window.setTimeout() but it fails saying getBrSeoData was never called.
This is my code
useEffect(() => {
if (!Config.ui.isBot) {
window.setTimeout(() => {
getBrSeoData(productType, productId, productName, productStatus);
}, BR_DELAY);
}
}, [getBrSeoData, productType, productId, productName, productStatus]);
This is the test
it("should make the blooomreach api call if !Config.ui.isBot", done => {
const BR_DELAY = 6000;
const response = {
status: "SUCCESS",
payload: {
"related-item": bloomreachState["related-item"],
"related-category": [],
"more-results": []
}
};
props = {
relatedSearches: bloomreachState["related-item"],
relatedCategories: bloomreachState["related-category"],
relatedProducts: bloomreachState["more-results"],
getBrSeoData: sinon.spy(() => new Promise(resolve => resolve({ response })))
};
Config.ui.isBot = false;
component = render(<BloomreachSeo {...props} />);
window.setTimeout(() => {
expect(props.getBrSeoData).to.have.been.calledOnce;
}, BR_DELAY);
done();
});
Istanbul showing no coverage for the line
I was able to get it working by using the npm package lolex. If anyone has issues with it using react testing library along with testing a window.setTimeout()
let clock;
beforeEach(() => {
clock = lolex.install({ now: 4476701471000, toFake: ["setTimeout"] });
});
afterEach(() => {
clock.uninstall();
});
it("should make the bloomreach api call if !Config.ui.isBot", () => {
const BR_DELAY = 5000;
const response = {
status: "SUCCESS",
payload: {
"related-item": bloomreachState["related-item"],
"related-category": [],
"more-results": []
}
};
props = {
relatedSearches: bloomreachState["related-item"],
relatedCategories: bloomreachState["related-category"],
relatedProducts: bloomreachState["more-results"],
getBrSeoData: sinon.spy(() => new Promise(resolve => resolve({ response })))
};
Config.ui.isBot = false;
component = render(<BloomreachSeo {...props} />);
clock.tick(BR_DELAY);
clock.setTimeout(() => {
expect(props.getBrSeoData).to.have.been.called;
}, BR_DELAY);
});
i need to run the AsyncStorage first before run the other functions , but AsyncStorage take a time to run and it give the resutlt in the end of the code
constructor(props) {
super(props);
AsyncStorage.getItem(TOKEN).then((r) => {
console.log("AsyncStorage Function")
})
this.state = { token: null }
console.log("The last Function")
}
Ideally you shouldn't be performing any async tasks inside a constructor assuming this is a react class component.
So if you wish to perform any async tasks you could make use of componentDidMount from the component lifecycle method.
But in general if you want to wait for the async operation then this can be achieved in different ways
Using Promises
const promiseFun = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success")
}, 1000)
})
}
promiseFun().then((res) => {
//here res will be what is returned from the promise function
console.log(res);
//All the required things that needed to be perfomed after the async operation should be placed here.
});
//Whatever the actions/statements written here will be executed before the async operation is finished
console.log("Before Promise Returned");
Using async/await
const promiseFun = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Success")
}, 1000)
})
}
const execFun = async() => {
let res = await promiseFun();
//All the statments from here will be executed only after the promise/async function returned.
console.log(res);
}
execFun();
//All the statements here will be executed without waiting for the promise/async function gets executed
console.log("Before Promise Returned");
I have an api call where its results are being dispatched through redux state update. I have added a setTimeOut after fetching results so that results are dispatched only after a delay of 15 seconds. For first api call the setTimeOut will get executed after 15 seconds but when second time api call is being made setTimeOut is executed right away.
I have seen the posts where it says it will run only once. But its supposed to run again when called right ?
What I understood is that setTimeOut will create a delay of desired time and after that delay it will run and stop there. But when called again it would again create a delay and excuse after that right ?
This is my code
export const getVenuesBonusOffer = (venueId, uid, accessToken) => {
return (dispatch, getState) => {
let header = getHeader(
CONTENT_TYPES.application_URLencoded,
uid,
accessToken
);
let url = VENUE_BONUS_OFFER + venueId;
__DEV__ && console.log("url: ", url, " header: ", header);
_GET(url, header)
.then(res => res.json())
.then(res => {
//__DEV__ && console.log("Res for getVenuesBonusOffer: ", res);
if (res.message === API_RESPONSE_MESSAGE.NO_BONUS_OFFER_EXIST) {
//showAlert("OOPS", res.message, "danger", false);
dispatch(venuesBonusOfferNotExist());
} else {
this.timeoutid = setTimeout(() => {
__DEV__ && console.log("timeout");
dispatch(venuesBonusOfferExist(res.data));
clearTimeout(this.timeoutid);
}, 15000);
}
})
.catch(err => {
__DEV__ && console.log("err for getVenuesBonusOffer: ", err);
});
};
};
EDIT 1
please note this is not called from class but its within an const like export const something
EDIT 2
from class called RunAR.js the above method is called. Called from componentWillReceiveProps
componentWillReceiveProps(nextProps, nextState) {
if (this.props.venuesBonusOfferDuration === 0) {
this.subscribe.unsubscribe();
}
if (nextProps.showBonusObj) {
let milliseconds = nextProps.venuesBonusOfferDuration * 1000;
this.source = timer(milliseconds);
const subscribe = this.source.subscribe(val => {
this.props.hideBonusOffer();
const timerForBonusApi = bonusApiTimer.subscribe(val => {
console.log("caling timer");
this.props.getVenuesBonusOffer(
this.props.venues.venues.id,
this.props.uid,
this.props.accessToken
);
});
});
}
}
function mapDispatchToProps(dispatch) {
return {
hideUserMenu: () => dispatch(hideUserMenu()),
showingVenueLoaderinAR: data => dispatch(showingVenueLoaderinAR(data)),
isPopupActiveFun: data => dispatch(isPopupActiveFun(data))
};
}
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
I have the following code:
callAPI() {
return this.myModel.map(action(retValue => {
return this.myApi.doWork(retValue.Input).then(action(model => {
this.model.Input = Object.assign({}, model);
this.saveState();
})).catch(error => {
throw(error);
});
}));
if my client code I am doing something like this:
myStore.callAPI().then(() => {
console.log("completed");
this.setState({ loading: false });
});
I am getting an error
.then() is not a function
I want to get a callback when all async operations are completed.
Please use Promise.all to await multiple promises and return the result which is another Promise which you can call .then on.
In order to always return a promise after an imperative block of code you can return Promise.resolve() that will make the code chainable.
callAPI() {
return Promise.all(
this.myModel.map(action(retValue => (
this.myApi.doWork(retValue.Input).then(action(model => {
this.model.Input = Object.assign({}, model);
this.saveState();
return Promise.resolve();
}))
)));
);
}
Please see an example:
const sleep = timeout => new Promise(resolve =>
setTimeout(() => resolve(timeout), timeout)
);
const tap = timeout => {
console.log(`Task completed after ${timeout}ms`);
return timeout;
}
const tasks = [
sleep(1000),
sleep(2000),
sleep(500),
sleep(30),
sleep(2500),
sleep(450)
].map(task => task.then(tap));
Promise.all(tasks).then(x => {
console.log(x);
});