This reply is quite specific to react-testing-libaray but I think the concept is generally applicable.
First of all I realised that if you start the async operation in response to a user event (say a click) then you don't have a problem - as soon as you've called fireEvent.click
, you are in state 3, then you can use waitFor()
to wait until you get to state 4.
Secondly, if your environment gives you a handle to the promise for the async operation then of course you can just wait for that so again you don't have a problem.
In my case, the async operation was fired off by a timer, and because it was inside a react component, I get a handle to that timer from the outside. So my scenario is something like this:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "initial" };
}
componentDidMount = () => setTimeout(async () => {
this.setState({ message: "loading" });
var message = await this.props.longRunningOperation();
this.setState({ message });
}, 5000);
render = () => <div>{this.state.message}</div>
}
The component state is initially "initial", 5 seconds after it mounts, it fires off the long running operation and sets the state to "loading", then when it gets a response, it sets the state to the text of that response. My test case looks like this:
test("long running response", async () => {
const fakeLongRunningOperation = async () => new Promise(resolve =>
setTimeout(() => resolve("success"), 2000));
jest.useFakeTimers();
var app = render(<App longRunningOperation={fakeLongRunningOperation} />);
jest.advanceTimersByTime(4000)
expect(app.queryByText("initial")).not.toBeNull();
jest.advanceTimersByTime(2000)
expect(app.queryByText("loading")).not.toBeNull();
jest.advanceTimersByTime(2000)
await waitFor(() => { expect(app.queryByText("success")).not.toBeNull() });
});
We fake the long running operation with a function that returns "success" after a 2 second delay. After 4 seconds, we expect the state to still be "initial", then after another 2 seconds we expect it to be "loading" and after another 2 seconds, we expect it to be "success". And we use jest fake timers so the test doesn't actually take 8 seconds to run.
Hope that helps anyone else who is trying to do a similar kind of thing.