Given the following code, I'm not clear on how I should be using enzyme to test componentWillMount (or DidMount, same situation). I've tried using the update() method but it is executed before my Promise is resolved. I've also tried both shallow and mount APIs but got the same problem, my Promise is resolved too late and my assertion fails because setState is never called.
Any help would be appreciated.
export default class MyComponent {
componentWillMount() {
someAsyncLibrary.fooApi().then(response => {
this.setState({
happy: response.happy,
});
});
}
}
export default {
fooApi() {
return Promise.resolve({ happy: 'i-am-now-happy' });
}
}
import { shallow } from 'enzyme';
import MyComponent from '../my-component';
jest.mock('../some-async-library');
describe('MyComponent', () => {
it('should set state on mount', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.state('happy')).toEqual('i-am-now-happy');
});
});
In your test, require in your jest-mocked async lib, and then:
const wrapper = shallow(<MyComponent />, { lifecycleExperimental: true });
return fooAPI().then(() => {
expect(wrapper.state('happy')).toEqual('i-am-now-happy');
});
That way, you can be sure your assertion runs after the fooAPI promise has resolved.
Thanks for that, I'll try it out! I do have a new question, though, I don't know what lifecycleExperimental is and I couldn't find it in the docs, could you tell me what it actually does?
When it's set to true, shallow will run the lifecycle methods you'd expect, including componentWillMount. In the next major version, it'll be enabled by default.
This pattern works as long as there are no arguments to the fooAPI call inside my lifecycle method. If I need to pass anything to the call then this pattern breaks down because I'm no longer testing the actual code, I would need to perform the call by passing in the parameters within my unit-test and then check the outcome. Any suggestions on how this could be achieved?
If fooAPI is jest-mocked, you can handle it whether there's arguments or not. Of course you're not testing the actual code unless the mock calls through to the original - but you can certainly set it up to do that.
@liuuu in your example, assuming fooAPI() returns the same promise that would be used inside MyComponent, that should work fine.
any update on the lifecycleExperimental? does it also works for componentDidMount?
@JimmyLv as of enzyme v3, lifecycle methods are enabled by default, including componentDidMount.
You may try:
it('should set state on mount', (done) => {
const wrapper = shallow(<MyComponent />);
setImmediate(() => {
expect(wrapper.state('happy')).toEqual('i-am-now-happy');
done();
}, 0);
});
Since setImmediate is a macro-task, it should run after the callback of the Promise which is a micro-task.
I've used process.nextTick(callback) with some success. From the docs:
The
process.nextTick()method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to completion, all callbacks currently in the next tick queue will be called.
Example usage:
it('should set state on mount', (done) => {
const wrapper = shallow(<MyComponent />)
process.nextTick(() => {
wrapper.update() // if you need to assert changes in the DOM resulting from state change
expect(wrapper.state('happy')).toEqual('i-am-now-happy')
done()
})
})
Assume this is my component: Rendering list of users
export default class MyComponent {
componentWillMount() {
someAsyncLibrary.fooApi().then(response => {
this.setState({
userList: response.userList,
});
});
}
render(){
return(
<ul>
userList.map((user) => {
return <li>{user.name}</li>
})
</ul>
)
}
}
After the ajax request, i am updating a state with latest userList. To test this component, instead of checking userList for latest values, i trying to test by finding number of <li> tags with this component like below.
it('should set state on mount', (done) => {
const wrapper = shallow(<MyComponent />)
process.nextTick(() => {
expect(wrapper.find('ul > li').length).toEqual(1);
done()
})
})
This one fails, but when i test against state values , test is passes.
Can we do this like this way.
@saravanan10393 you may need to force update wrapper.
it('should set state on mount', (done) => {
const wrapper = shallow(<MyComponent />)
process.nextTick(() => {
wrapper.update();
expect(wrapper.find('ul > li').length).toEqual(1);
done()
})
})
@DavidOnGitHub is right. I'll update my comment to include wrapper.update()
I'm going to close this; please file a new issue if there's more to discuss.
While a workaround rather than a solution to the problem: it is also an option to extract the code inside the lifecycle methods into separate methods, and then test these method instead. That gives the benefit of naming whatever happens inside the lifecycle method, but hides the connection between the lifecycle method and the action itself.
So instead of:
componentWillMount() {
callSomeApi()
.then(..)
.then(..)
}
We could say:
componentWillMount() {
loadDataForApp()
}
loadDataForApp() {
return callSomeApi()
.then(..)
.then(..)
}
The latter example could then be testet with standard techniques, e.g like this when using Jest.
Most helpful comment
You may try:
Since setImmediate is a macro-task, it should run after the callback of the Promise which is a micro-task.