Cannot access (async) callback function Jest - reactjs

I am trying to test the esri promise with Jest & Enzyme
import { esriPromise } from 'react-arcgis';
componentWillMount(){
this.setState({name: 'JAKE'});
this.addTileLayer();
};
addTileLayer(ext, url) {
this.setState({name: 'Amber'});
let promise = esriPromise(['esri/layers/TileLayer']).then(([TileLayer]) => {
this.setState({name: 'THISONE'});
let tileLayer = new TileLayer();
this.setState({
layers: [...this.state.layers, tileLayer]
})
}).catch((err) => console.error(err));
this.setState({prmoise: promise})
};
In my test,
test('Promise states check', async () => {
const map = shallow(<Map />)
await map.instance().componentWillMount();
console.log(map.state());
});
But it always prints, promise: Promise { < pending > } } Seems like it's not going inside the esriPromise function. (I have also tried done() which was unsuccessful as well)
Any tips would be greatly appreciated!

Try returning the esriPromise in the addTileLayer function

Related

Can't seem to get test coverage for a setTimeout action dispatched inside of an useEffect

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);
});

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 test a method which is resolving a promise and then changing state in React?

I need to test that when button is clicked and after promise resolve
state.message === 'loggedIn successfully'
class Login extends Component {
constructor() {
this.onLoginClick = this.onLoginClick.bind(this);
}
fetchLogin() {
return new Promise(function (resolve) {
reollve({ success: true });
})
}
onLoginClick() {
let that = this;
fetchLogin().then(function ({ success }) {
success ?
that.setState({ message: 'loggedIn successfully' }) :
that.setState({ message: 'Fail' });
})
}
render() {
return (<div>
<button onClick={this.onLoginClick}></button>
</div>)
}
}
I guess you are aware of jest simulate in order to simulate your click ( if not then simulate)
You should be able to use jest async/await or promises with jest, here is the link to the official doc
It should be something like this :
it('works with async/await and resolves', async () => {
const wrapper = mount(<Login />);
wrapper.find('button').simulate('click');
await expect(wrapper.state().message).resolves.toEqual('loggedIn successfully');
});
Since you are performing a test on a promise, you should wait for the function triggered by the button to execute before you can make the assertion (thus the await).
The code below should work for you case:
it('should change state on successful login', async () => {
const wrapper = mount(<Login />);
await wrapper.find('button').simulate('click');
expect(wrapper.state().message).toEqual("loggedIn successfully");
});

AsyncStorage.getItem in react native not working as expected

I am trying to fetch data using AsyncStorage. whenever i call my action creator requestData and do console on the data which is passed , i get something like below .I have two version of getItem .In both the version i get useless value for property field . Property value should be readable
{"fromDate":"20160601","toDate":"20160701","property":{"_40":0,"_65":0,"_55":null,"_72":null},"url":"/abc/abc/xyz"}
async getItem(item) {
let response = await AsyncStorage.getItem(item);
let responseJson = await JSON.stringify(response);
return responseJson;
}
async getItem(item) {
try {
const value = AsyncStorage.getItem(item).then((value) => { console.log("inside componentWillMount method call and value is "+value);
this.setState({'assetIdList': value});
}).then(res => {
return res;
});
console.log("----------------------------value--------------------------------------"+value);
return value;
} catch (error) {
// Handle errors here
console.log("error is "+error);
}
}
componentWillMount() {
requestData({
fromDate: '20160601',
toDate: '20160701',
assetId: this.getItem(cmn.settings.property),
url: '/abc/abc/xyz'
});
}
You are getting property as a promise, you need to resolve it.
Try to use something link that.
assetId: this.getItem(cmn.settings.property).then((res) => res)
.catch((error) => null);
Since AsyncStorage is asynchronous in nature you'll have to wait for it to return the object AND THEN call your requestData method; something like the following -
class MyComponent extends React.Component {
componentWillMount() {
this.retrieveFromStorageAndRequestData();
}
async getItem(item) {
let response = await AsyncStorage.getItem(item);
// don't need await here since JSON.stringify is synchronous
let responseJson = JSON.stringify(response);
return responseJson;
}
async retrieveFromStorageAndRequestData = () => {
let assetId = await getItem(cmn.settings.property);
requestData({
fromDate: '20160601',
toDate: '20160701',
assetId,
url: '/abc/abc/xyz'
}) ;
}
// rest of the component
render() {
// render logic
}
}

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