Leverage Rewire for Unit Testing in Node.js

Unit testing in Node.js can be challenging. After trying various libraries, I found Rewire to be the most practical for testing purposes

I'll start with a full disclosure - Up until now, I have only written unit tests using Jest. However, at my current job, I had the opportunity to work on a small project that allowed me to choose a new testing library.

After some research, I decided to go with Mocha as a test runner and Chai for assertions. Then I reached the fun part - Mocking and Spying. The most obvious choice was Sinon, which works great as a spy (for example, if you want to know whether a certain log was printed). However, when it came to Mocking (or Stubbing), it wasn't easy at all.

If you're interested - I found this article back then and I found it to be quite true (why is it so hard to test in NodeJS)

After struggling with Sinon, I turned to the Discord Node.js community with a specific question:

discordquestion

The first response was a link to the rewire repository. I must say, once I started using it, I couldn't stop. It made mocking incredibly easy.

To install it simply run:

npm install rewire

import it:

const rewire = require('rewire');

To initialize it, you need to "rewire" the module that contains the functions, methods, or variables you'd like to mock.

const mockedIndex = rewire('../../index');

So let's look at some examples:

I'll start with the original question I raised in the Node Discord server. Here's my original function (just for the sake of the example):

async function updatePolicy(project, policy, file) {
  return Promise.allSettled(Object.entries(file).map(([status]) => {
    try {
      if (!countryRules[country]) {
        return Promise.resolve();
      }
      if (statusDisable(status))
        console.log('disable');
      else if (statusEnable(status))
        console.log('enable');
        [...]
}

I wanted a simple test case to check that the correct function is called given the right arguments. Since I had many test cases like these, I created a more "generic" function that receives the function I'd like to test as an argument.

// rewire the module
const lib = rewire('../../index');

// generic function

const checkFunctionCall = (functionName, done, file) => {
    lib.__set__(`${functionName}IsCalled`, false); // setting the function under test to isCalled false
    lib.__set__(functionName, () => { // function under test is called
        lib.__set__(`${functionName}IsCalled`, true); // changing the isCalled to true in this case.
    });
    lib.updatePolicy(project, policy, file); // Calling the function that eventually calls the function i'd like to assert on
    assert.equal(lib.__get__(`${functionName}IsCalled`), true); // asserting that isCalled is true
    done();
};

// test case

it('statusEnable function called', (done) => {
    let file = "example argument";

    checkFunctionCall('statusEnable', done, file);
});

Doesn't seem so complicated, right? Moving on to mocking of an async function that should return resolved promise. Let's say this is the original function:

async function persistCatalogs(bucketName, catalogs) {
  const catalogBucket = storageClient.bucket(bucketName);
  return new Promise(async (resolve, reject) => {
    for (const chunk of keyChunks) {
      await Promise.all(chunk.map(async (catalog) => uploadCatalog(catalogBucket, cacheTtl, catalog, catalogs[catalog])));
    }
    resolve();
  })
  .then(() => logger.info("Catalog uploaded!"));
}

I'd like to test that given a specific argument, the uploadCatalog function was called 12 times.

// stubbed function that will be called when mocking the uploadCatalog function
function uploadCatalogStub() {
  return Promise.resolve('Mocked result');
}
//test case
it("should upload each catalog in chunks", async () => {
    lib.__set__("uploadCatalog", uploadCatalogStub); // rewire will set uploadCatalogStub once uploadCatalog is called
    await lib.persistCatalogs(bucketName, catalogs); // Original function called
    expect(uploadCatalogStub.callCount).to.equal(12); // assertion
});

Moving on the mocking a global variable that eventually will be used in a function.

This is the function i'd like to test:

function addEventToCatalog(rawEvent, catalog) {
  rawEvent.countryAvailability.forEach(c => {
    if (COUNTRIES_LIST.includes(c))
      catalog[c].push(event);
    else
      logger.warn(`Unsupported country ${c} on event ${event.assetId}, skipping for catalog`);
  });

Now COUNTRIES_LIST variable is a global variable, which it's data in it might effect the result of the test. So let's mock it:

lib.__set__('COUNTRIES_LIST', structuredClone(countryCodesData)); //COUNTRIES_LIST is a global variable, here it'll return a clone of another data i chose.

and call the tested function is test as usual:

it('should add event to US catalog if countryAvailability includes US', () => {
    lib.addEventToCatalog(rawEvent, catalog);

    expect(catalog.US).to.have.lengthOf(1); // assertion
});

In conclusion, rewire proved to be the simplest mocking library I've had the opportunity to use in Node.js projects. Ultimately, pairing it with a dynamic test runner like Mocha made for an easy choice compared to other libraries.

As a result, I ended up replacing all other mocks, such as Sinon Stubbing, with Rewire and haven't looked back since.



Tags:

Related Articles

Lab as a Service in DAZN

Read More

Integrating Azure AD Authentication in Your Next.js App Using MSAL

Read More

GitHub Actions for Dynamic Cross-Platform Testing

Read More

Powerful Pytest Parametrization

Read More