Labs ICT
Pro Login

Testing Callbacks

The old-school way of handling async.

Testing Callbacks

Not every function returns a value. Some functions take a callback and do something with it. Testing these felt weird to me at first because you do not have a return value to check. But Jest has a clean way to handle this.

Here is the thing. Callbacks are still common in Node.js and older codebases. You need to know how to test them even if you prefer async/await.

The done Callback

Jest gives you a done parameter you can add to your test function. When you add done, Jest waits until you call it before finishing the test.


function fetchData(callback) {
  setTimeout(() => {
    callback(null, { name: 'Alice' });
  }, 100);
}

it('fetches data with callback', (done) => {
  fetchData((err, data) => {
    expect(err).toBeNull();
    expect(data.name).toBe('Alice');
    done();
  });
});
    

If you forget to call done, the test will time out. That is Jest telling you it is still waiting for something to finish.

Testing Error Callbacks

You also need to test the case where something goes wrong. The callback receives an error as the first argument.


function fetchData(callback) {
  setTimeout(() => {
    callback(new Error('Network error'));
  }, 100);
}

it('passes error to callback', (done) => {
  fetchData((err, data) => {
    expect(err).toBeDefined();
    expect(err.message).toBe('Network error');
    expect(data).toBeUndefined();
    done();
  });
});
    

Using Promises Instead

If you can, wrap callbacks in promises and test with async/await instead. It is cleaner and easier to read.


function fetchDataPromise() {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve({ name: 'Alice' }), 100);
  });
}

it('fetches data with promise', async () => {
  const data = await fetchDataPromise();
  expect(data.name).toBe('Alice');
});
    
Try it Yourself →

Key Takeaways

  • Add done as a parameter to tell Jest to wait for callback completion
  • Always call done() at the end of your callback, or the test times out
  • Test both success and error cases with callbacks
  • Promises with async/await are usually cleaner than testing callbacks directly