I am trying to test an async function in a react native app.
class myClass extends React.Component {
...
closeModal = async () => {
if (someCondition) {
await myFunction1();
} else {
await myFunction2();
}
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('Main');
};
...
}
This is my test:
const navigation = {
navigate: jest.fn(),
state: { params: { onGoBack: jest.fn() } },
};
const renderComponent = overrides => {
props = {
navigation,
...overrides,
};
return shallow(< myClass.wrappedComponent {...props} />);
};
describe('When the user presses the close icon', () => {
it('should close the modal', () => {
const wrapper = renderComponent();
const instance = wrapper.instance();
const spyCloseModal = jest.spyOn(instance, 'closeModal');
instance().forceUpdate();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
expect(spyCloseModal).toHaveBeenCalled(); // this is passed
expect(navigation.navigate).toHaveBeenCalled(); // this is not passed
});
});
It looks like it gets stuck on the await calls. If I remove the await calls then it passes. Someone mentioned in another post to use .and.callThrough
after spyOn but it gives me this error
Cannot read property 'callThrough' of undefined
we practically combine all the solution ( async/await , promise based and callback done ) for async test in the test file. Let's use the recent solution using async/await so it will be: describe('testing users ', () => { it('can get all users', async () => { const response = await chai.
Jasmine supports three ways of managing asynchronous work: async / await , promises, and callbacks.
In order to mock asynchronous code in Jest, more specifically Promises, you can use the mockResolvedValue function. This will mock the return value of the Promise to be 42. In order to test a Promise in Jest, you need to turn your it block into async in order to use the await keyword in front of an expect statement.
Jest waits until the done callback is called before finishing the test. test('the data will be peanut butter', done => { function callback(data) { expect(data). toBe('peanut butter'); done(); } fetchData(callback); }); In the case where done() is never called, the test fails, which is exactly what you want to happen.
one of solution is to make your test async
and run await (anything)
to split your test into several microtasks:
it('should close the modal', async () => {
const wrapper = renderComponent();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
await Promise.resolve();
expect(navigation.state.params.onGoBack).toHaveBeenCalled();
expect(navigation.navigate).toHaveBeenCalledWith("Main");
});
I believe you don't need either .forceUpdate
nor .spyOn
on instance method. once navigation happens properly it does not matter by what internal method it has been called
more on microtask vs macrotask: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f
alternative is to use macrotask(setTimeout(...., 0)
)
it('should close the modal', (done) => {
const wrapper = renderComponent();
component
.find({ testID: 'close-icon' })
.props()
.onPress();
setTimeout(() => {
expect(navigation.state.params.onGoBack).toHaveBeenCalled();
expect(navigation.navigate).toHaveBeenCalledWith("Main");
done();
});
}
Yes, you're on the right track...the issue is that closeModal
is asynchronous.
The await
hasn't finished by the time execution returns to the test so this.props.navigation.navigate
hasn't been called yet.
The test needs to wait for closeModal
to complete before asserting that navigate
has been called.
closeModal
is an async
function so it will return a Promise
...
...and you can use the spy to retrieve the Promise
it returns...
...then you can call await
on that Promise
in your test to make sure closeModal
has completed before asserting that navigate
has been called.
Here is a simplified working example to get you started:
import * as React from 'react';
import { shallow } from 'enzyme';
class MyClass extends React.Component {
closeModal = async () => {
await Promise.resolve();
this.props.navigation.navigate('Main');
}
render() { return <div onClick={() => this.closeModal()}></div> }
}
test('MyClass', async () => { // <= async test function
const props = { navigation: { navigate: jest.fn() }};
const wrapper = shallow(<MyClass {...props} />);
const instance = wrapper.instance();
const spyCloseModal = jest.spyOn(instance, 'closeModal');
wrapper.find('div').simulate('click');
expect(spyCloseModal).toHaveBeenCalled(); // Success!
const promise = spyCloseModal.mock.results[0].value; // <= get the Promise returned by closeModal
await promise; // <= await the Promise
expect(props.navigation.navigate).toHaveBeenCalled(); // Success!
})
Note the use of mockFn.mock.results
to get the Promise
returned by closeModal
.
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