Waiting for that important call

Originally published Mar 14, 2018·Tagged javascript, testing

Sometimes while testing, it's necessary to wait until a function has been called. Maybe you're testing code with [Node-style callbacks](http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/); maybe you're working with a [React render prop](https://reactjs.org/docs/render-props.html). Regardless of how you got there, your test needs to pause until some function has been called. It's possible to wait for a promise to be fulfilled, but how do you wait until an arbitrary function has been called? ## The problem Suppose your test looks like this: ```js const createEmitterOfSomeSort = require('./myEmitter'); it('should do the thing', async () => { const emitter = createEmitterOfSomeSort(); const callback = jest.fn(); emitter.on('my-event', callback); // TODO: wait for the callback to be called before proceeding // Check values which will only change after the given event expect(emitter.color).toBe('blue'); }); ``` This test needs to wait for `my-event` to be fired asynchronously before the color gets set. Otherwise, the test prematurely races through to its completion. It's possible to wrap this all in a Promise which will resolve when your event is fired. I've done this loads of times in tests; it's tedious! It's also a pain to refactor. Suppose you want to wait for the event to fire 5 times instead of just once. This requires additional work and added complexity to your test. ## My attempted solution I decided to write and publish my solution as the [`anticipated-call`](https://www.npmjs.com/package/anticipated-call) package. This utility is capable of wrapping any function, and gives you an easy way to obtain a promise which resolves once the function has been called. Here's an example of how you might use it in a test: ```js const anticipated = require('anticipated-call'); const createEmitterOfSomeSort = require('./myEmitter'); it('should do the thing', async () => { const emitter = createEmitterOfSomeSort(); const callback = anticipated(jest.fn()); emitter.on('my-event', callback); await callback.nextCall; // Check values which will only change after the given event expect(emitter.color).toBe('blue'); }); ``` The `await` statement is the magic sauce: it'll pause the test's execution until the callback is called. Now, if you decide the event needs to be fired 5 times instead of just once, it's simple to update your tests: ```js await callback.nthNextCall(5); ``` ## Testing React render props This package has helped me the most when I'm writing render-prop components. Suppose you have a component responsible for fetching data that's used like this: ```jsx (<MyTweetFetcher render={({isLoading, username, tweets}) => ( <h2>{isLoading ? 'Loading...' : username}</h2> <ul> {tweets.map((tweet) => ( <li key={tweet.id}>{tweet.content}</li> )} </ul> ) />) ``` These components commonly call the render prop multiple times in response to asynchronous operations. This behavior creates a problem for writing tests: you need to make sure that the callback received the correct arguments, but you can't perform that check until the component has been rendered. `anticipated-call` comes to the rescue: ```jsx const Enzyme = require('enzyme'); const anticipated = require('anticipated-call'); const MyTweetFetcher = require('./MyTweetFetcher'); it('should call the render prop with the correct arguments', async () => { // The render prop needs to return a valid React node, so use `null` here. const renderProp = anticipated(jest.fn(() => null)); // The `nextCallDuring` method allows you to tell `anticipated-call` that // the function should be called as a result of running the passed callback. await renderProp.nextCallDuring(() => { Enzyme.mount(<MyTweetFetcher render={renderProp} />); }); // The render prop will initially be called while data is loading. expect(renderProp.mock.calls[0].isLoading).toBe(true); // Wait for the render prop to be called again, after the data has loaded. await renderProp.nextCall; expect(renderProp.mock.calls[1].isLoading).toBe(false); expect(renderProp.mock.calls[1].tweets).toBeInstanceOf(Array); }); ``` ## Friendlier testing This package is [pretty small](https://github.com/r24y/anticipated-call/blob/develop/src/index.js); it does nothing that can't already be done with a bit of Promise-wrangling. However, its appeal lies in the fact that you no longer _have_ to engage in any Promise-wrangling. When I need to wait for a callback, I throw `anticipated-call` at it and save my energy for more difficult problems. > Check out [`anticipated-call`](https://www.npmjs.com/package/anticipated-call) on npm and submit PRs or issues on [Github](https://github.com/r24y/anticipated-call) if you have ideas for improving it!