How to Mock the async NPM Library Using Jest (With Examples)

As a senior developer, one of the first things I teach my team is this: "Write tests like your CI/CD pipeline depends on it—because it does." When working with legacy or third-party libraries like async, mocking them correctly with Jest becomes essential for writing clean and deterministic unit tests.

In this post, I’ll show you how to mock the async NPM package using Jest, walk through real-world examples, and explain a few caveats I’ve learned along the way.


🔍 Why Mock the async NPM Module?

async is a utility module that provides straight-forward, powerful functions for working with asynchronous JavaScript. Popular functions include:

  • async.series()
  • async.parallel()
  • async.waterfall()

If you're testing a function that relies on async.series() (or similar), you probably don't want to test the behavior of the library—you want to test your code’s response to the outcome. That’s where mocking comes in.


🧪 Setting Up Jest for Mocking

Before mocking, make sure you have Jest installed:

npm install --save-dev jest

Also install async if it's not already:

npm install async

✅ Example: Mocking async.series() with Jest

Let’s say you have a module like this:

// taskRunner.js
const async = require('async');

function runTasks(callback) {
  async.series([
    function (cb) {
      setTimeout(() => cb(null, 'Task 1'), 100);
    },
    function (cb) {
      setTimeout(() => cb(null, 'Task 2'), 100);
    }
  ], callback);
}

module.exports = { runTasks };

You want to test the behavior of runTasks() without waiting for the real timeouts.

🧩 Here’s how you mock async.series():

// __tests__/taskRunner.test.js
jest.mock('async', () => ({
  series: jest.fn()
}));

const async = require('async');
const { runTasks } = require('../taskRunner');

describe('runTasks', () => {
  it('calls async.series with correct arguments', () => {
    const mockCallback = jest.fn();

    async.series.mockImplementation((tasks, callback) => {
      callback(null, ['Mock Task 1', 'Mock Task 2']);
    });

    runTasks(mockCallback);

    expect(async.series).toHaveBeenCalledTimes(1);
    expect(mockCallback).toHaveBeenCalledWith(null, ['Mock Task 1', 'Mock Task 2']);
  });
});

⚠️ Gotchas When Mocking async

  1. Ensure you mock before importing the module under test. If async is imported and cached before mocking, it won’t work.

  2. Use jest.requireActual if partial mocking is needed:

jest.mock('async', () => {
  const original = jest.requireActual('async');
  return {
    ...original,
    series: jest.fn()
  };
});
  1. Avoid mocking too deeply. Only mock what you need; otherwise, you may introduce noise in your tests or hide bugs.

💡 Pro Tips from a Senior Dev

  • Prefer dependency injection if you plan to mock frequently-used libraries. This makes your code more testable and modular.
  • Use spies if you want to mock specific behavior but retain original functionality.
  • Write integration tests that use the real async module as well, so you're not mocking away all your coverage.

🔚 Conclusion

Mocking async with Jest is straightforward once you understand how module mocking works. It’s a powerful pattern that can save you from flaky tests and speed up your test suite dramatically.

TL;DR:

  • Use jest.mock('async')
  • Replace async.series() (or any other method) with a mock implementation
  • Test how your code responds, not the library itself