In my create-react-app, I am trying to test a component that does multiple setState
s when mounted.
class MyComponent extends React.Component {
state = {
a: undefined,
b: undefined,
c: undefined,
};
fetchA() {
// returns a promise
}
fetchB() {
// returns a promise
}
fetchC() {
// returns a promise
}
async componentDidMount() {
const a = await fetchA();
this.setState({ a });
}
async componentDidUpdate(prevProps, prevState) {
if (prevState.a !== this.state.a) {
const [b, c] = await Promise.all([
this.fetchB(a);
this.fetchC(a);
]);
this.setState({ b, c });
}
}
...
}
In my test, I do something like this, trying to let the setState
in componentDidUpdate
to finish before making assertions.
import { mount } from 'enzyme';
describe('MyComponent', () => {
const fakeA = Promise.resolve('a');
const fakeB = Promise.resolve('b');
const fakeC = Promise.resolve('c');
MyComponent.prototype.fetchA = jest.fn(() => fakeA);
MyComponent.prototype.fetchB = jest.fn(() => fakeB);
MyComponent.prototype.fetchC = jest.fn(() => fakeC);
it('sets needed state', async () => {
const wrapper = mount(<MyComponent />);
await Promise.all([ fakeA, fakeB, fakeC ]);
expect(wrapper.state()).toEqual({
a: 'a',
b: 'b',
c: 'c',
});
});
});
Here's the interesting part: My test above will fail because the last setState
call (in componentDidUpdate
) has not finished when the assertion is made. At that time, state.a
is set, but state.b
and state.c
is not set yet.
The only way I could make it work is by wedging await Promise.resolve(null)
right before the assertion to give the last setState
that extra tick/cycle to complete. This looks too hacky.
Another thing I've tried is wrapping the assertion in setImmediate()
, which works fine as long as the assertion passes. If it fails, it will terminate the whole test because of uncaught error.
Has anyone overcome this problem?
This is how I solved it. Hope that helps someone.
import { mount } from 'enzyme';
describe('MyComponent', () => {
const fakeA = Promise.resolve('a');
const fakeB = Promise.resolve('b');
const fakeC = Promise.resolve('c');
MyComponent.prototype.fetchA = jest.fn(() => fakeA);
MyComponent.prototype.fetchB = jest.fn(() => fakeB);
MyComponent.prototype.fetchC = jest.fn(() => fakeC);
it('sets needed state', async (done) => {
const wrapper = mount(<MyComponent />);
await Promise.all([ fakeA, fakeB, fakeC ]);
setImmediate(() => {
// Without the try catch, failed expect will cause the
// whole test to crash out.
try {
expect(wrapper.state()).toEqual({
a: 'a',
b: 'b',
c: 'c',
});
} catch(error) {
done.fail(error);
}
done();
});
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With