-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to test component with async componentDidMount #1581
Comments
What is the implementation of
If let promise = Promise.resolve();
const loadData = () => {
return promise.then(mockedCallback);
};
shallowWrapper = shallow(<MyComponent loadData={loadData} />);
promise.then(() => {
shallowWrapper.update();
let containsSpinner = shallowWrapper.containsMatchingElement(<Spinner />);
expect(containsSpinner).to.be.false;
}); |
@koba04, my mockedCallback is indeed returning a Promise which resolves a mocked object. For your first solution, I managed to make it work with something similar using this : it('Should NOT render a progress component when loading data is done', () => {
shallowWrapper = shallow(<MyComponent loadData={mockedCallback} />);
setImmediate(() => {
shallowWrapper.update();
let containsSpinner = shallowWrapper.containsMatchingElement(<Spinner />);
expect(containsSpinner).to.be.false;
});
}); From what I understand, the setImmediate guarantees that its callback will be called AFTER any previously executed promise, so in my case it does work. For your second solution however, correct me if I am wrong but this won't work because even if the promise.then guarantees that the data has been returned back, nothing guarantees that the setState from within the component has been called, and nothing guarantees that the render() from the component is done either. I tried something similar doing this : it('Should NOT render a progress component when loading data is done', () => {
let spy = sinon.spy(mockedCallback);
shallowWrapper = shallow(<MyComponent loadData={spy} />);
let spyCall = spy.getCall(0);
spyCall.returnValue.then(() => {
shallowWrapper.update();
let containsSpinner = shallowWrapper.containsMatchingElement(<Spinner />);
expect(containsSpinner).to.be.false;
});
}); The code above does pretty much the same thing, it waits for the mockedCallback's promise to be completed before doing assertions, but it still fails because the setState/render from within the component isn't done yet which is understandable since we only waited for the promise, not necessarily for the render() to be called using the promise result. So far the setImmediate seems to be the way to go, I was just wondering if there was anything better. |
@spplante Ah, you are right. My second example doesn't work. let promise;
const loadData = () => {
promise = Promise.resolve().then(mockedCallback);
return promise;
};
shallowWrapper = shallow(<MyComponent loadData={loadData} />);
promise.then(() => {
shallowWrapper.update();
let containsSpinner = shallowWrapper.containsMatchingElement(<Spinner />);
expect(containsSpinner).to.be.false;
}); |
Anyway, this is not an issue of enzyme so stackoverflow might be a better place to ask the question. |
@koba04, then again I don't see how this would work, because the only thing your promise.then guarantees at this point, is to land exactly there : public componentDidMount() {
this.props.loadData().then((result) => {
// ---> Here you are after your promise.then(), the setState below hasn't necessarily been called and the render() neither
this.setState({ loading: false, data: result, error: null });
})
} So making assertions on the newly rendered content doesn't work since you did wait for the new data but not necessarily for the new data to be rendered properly. I ask the question here because it is pretty much the typical scenario of 90% of the React components and I can't find any reliable documentation on the best practices to test this typical scenario :
There should be a straight forward way to test this :| |
@spplante Did you try my example? const enzyme = require('enzyme');
const Adapter = require('enzyme-adapter-react-15');
const React = require('react');
enzyme.configure({adapter: new Adapter()});
const {shallow} = enzyme;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loaded: false,
};
}
componentDidMount() {
this.props.loadData().then((result) => {
this.setState({loaded: true, data: result});
});
}
render() {
return <div>{this.state.loaded ? this.state.data.foo : 'loading'}</div>
}
}
describe('#1581', () => {
it('should works fine', done => {
const mockedCallback = () => Promise.resolve({foo: 'bar'});
let promise;
const loadData = () => {
promise = Promise.resolve().then(mockedCallback);
return promise;
};
const wrapper = shallow(<App loadData={loadData} />);
expect(wrapper.text()).toEqual('loading');
promise.then(() => {
wrapper.update();
expect(wrapper.text()).toEqual('bar');
done();
});
});
}); The above test guarantee that the assertion is evaluated after calling mockedCallback, in this case, ShallowRenderer processes setState synchronously so the promise.then is processed after the setState and render. The behavior might be changed in the future version, but it works fine at the current version.
You can imagine the test flow like this. public componentDidMount() {
this.props.loadData().then((result) => {
// Here you are after your promise.then(), the setState below hasn't necessarily been called and the render() neither
this.setState({ loading: false, data: result, error: null });
})
.then(() => {
wrapper.update();
expect(wrapper.text()).toEqual('bar');
});
} |
@koba04 my mistake you are absolutely correct, it works. I also made it work using sinon to listen on mockedCallback which is pretty much the exact same thing as your example : it('Should NOT render a progress component when loading data is done', () => {
let spy = sinon.spy(mockedCallback);
shallowWrapper = shallow(<MyComponent loadData={spy} />);
let spyCall = spy.getCall(0);
spyCall.returnValue.then(() => {
shallowWrapper.update();
let containsSpinner = shallowWrapper.containsMatchingElement(<Spinner />);
expect(containsSpinner).to.be.false;
});
}); I guess I am misunderstanding how the Promise works in this particular scenario, because I just don't get how waiting for the original promise (which is only responsible for returning data) ALSO waits for the results to be rendered synchronously, how is that possible?
Edit : I totally get it the devil is in the details. The reason it works is because both .then() are stacked one after the other in the correct order on the same promise instance, the first .then being added by the component and the second .then being added by the unit test, thus the reason why the .then from the unit test is called at last. Thanks a lot for this, the issue can be closed 👍 |
Thank you! Please close the issue. (I can't close this because I don't have the permission 😅) |
@spplante |
@shivasai09 as of enzyme v3, the LifeCycleExperimental switch that we had to specify to the shallow constructor in order to run the lifecycle methods is now enabled by default and stable. See these posts to see what I am talking about : So yes, the componentDidMount does get called using the shallow. 👍 |
@spplante i have tried using your method , but the problem is when i write though i have written
that error is not handled how to tackle with this error |
Not sure I understand but on my side I initialize the spy in every single different test so there is no way I can have this error... |
@spplante i am also initializing the spy in every single different test..
but the problem is only occurring when i am using do one thing duplicate the this below test suite and run it , i mean to say write it two times and see if error occurs
and please do reply the what is the behaviour @spplante are you using karma framework? |
I am using karma, I am not a sinon or enzyme expert though, you might want to open an issue with sinon directly since this code is from a while ago and I don't recall having any problem with it :| |
I do not have any project running this code, I opened this issue to understand my error, I was able to make it work with sinon.spy just for theoretical reasons but I ended up using the following code which is more simple than using sinon.spy for my case : it('Should NOT render a progress component when loading entity relations is done', (done) => {
setImmediate(() => {
shallowWrapper.update();
let containsSpinner = shallowWrapper.containsMatchingElement(<Spinner />);
expect(containsSpinner).to.equal(false);
done();
});
}); Using the setImmediate fixed my original problem where I had to wait for 2 seconds using a timer. Back then I didn't understand why setImmediate was working and that's why I did it the long way with sinon but I don't have this code anymore and I don't feel like replicating the whole thing as I am currently working for a client. Please open an issue with sinon. |
@spplante thanks for the explianation
because i am not seeing any difference with the fix version of this code which is below
whats the difference why it dint worked? because @spplante form your explination then what's wrong in the first code? @spplante waiting for you reply, this one doubt will save my day.. |
Hard to tell honestly I would expect both of the codes to work |
@shivasai09 |
Sorry, i read the complete blog and with some guarantee that i might get an solution to my problem which is very similar to this and posted in: https://stackoverflow.com/questions/51550520/testing-promise-functions-in-jest In my example, the props are bind with |
I don't know if this will help anyone, but this pattern seems to work for me: componentWillMount() {
return Promise.all([this.promise1(), this.promise2()]);
} and in your specs: it('thing', async () => {
const willMount = spy(MyComponent.prototype, 'componentWillMount');
const wrapper = shallow(<MyComponent {...props} />);
await willMount;
wrapper.update().find(...)
}); I'm not sure why you need the wrapper.update since I would expect a re-render to trigger, but this works edit or even nicer const willMount = async (ComponentClass, props) => {
const willMount = spy(CreateCaseForm.prototype, 'componentWillMount');
const wrapper = shallow(<ComponentClass {...props} />);
await willMount;
return wrapper;
}; it('thing', async () => {
const wrapper = await willMount(MyComponent, props);
wrapper.update().find(...)
}); |
Posted this answer here:
|
If anyone is still over this, read these links: https://stackoverflow.com/a/53182054/5285338, jestjs/jest#2157 (comment), #346 (comment) (this one puts the first promise of the mount by sinon stub) |
I'm still trying to find a work around for this. None of the strategies I found in here worked for me. The well known |
@gil-air-may your test needs a way to get at the http request promise, so it can await on it. |
@gil-air-may I have the exact same issue/use case. Any resolve on this? I need to wait for at http request triggered from |
Inspired by https://stackoverflow.com/questions/49419961/testing-with-reacts-jest-and-enzyme-when-async-componentdidmount I added
|
@krichter722 adding |
Current behavior
I have a really typical dummy component which :
Typically, these are the relevant parts of the dummy component :
Looking at the code above, the following test is executing just fine :
In fact, the shallow does render with a spinner because the promise within componentDidMount didn't resolve immediately, which is totally fine for this test.
Now If I want to test the opposite, which is to make sure the Spinner does NOT render after the data has been loaded, what would be the best way to do this? I guess I could listen to the componentDidUpdate and validate once it has been called, but how would I do this? Here is an awefull bit of code that currently works just to illustrate the problem :
The test above works correctly, I just wait 2 seconds in order to wait for the promise to be completed, then I update the shallow and confirm that the progress isn't there anymore, but I do hope there is a cleaner way to do this as this is quite awful.
The pattern used by this dummy component really is typical, but somehow I just can't find any relevant documentation on how to test this kind of scenario :|
Would appreciate if someone could help 👍
Thanks!
API
Version
Adapter
The text was updated successfully, but these errors were encountered: