Jest Module Aliasing: A Debugging Journey in WooCommerce

Introduction

When working with large JavaScript codebases like WooCommerce, module aliasing is a common practice that helps maintain clean import paths. WooCommerce uses Jest for unit testing, which requires careful configuration to properly resolve these aliases. This post shares a recent debugging experience that highlights some subtle gotchas in Jest’s module name mapping and the moduleNameMapper option.

The Setup

In WooCommerce’s cart and checkout blocks, we use module aliases to map package names to specific paths in our codebase. I recently added a new alias:

"@woocommerce/blocks-checkout-events": "assets/js/events"

This follows our established pattern, where we have numerous aliases that help organize our code. Here’s a snippet of our module mapping configuration:

"moduleNameMapper": {
		"@woocommerce/atomic-blocks": "assets/js/atomic/blocks",
		"@woocommerce/atomic-utils": "assets/js/atomic/utils",
		"@woocommerce/blocks-checkout": "packages/checkout",
		"@woocommerce/blocks-checkout-events": "assets/js/events",
        ...
	},

The Mystery Error

While running unit tests, I encountered this error:

TypeError: Cannot read properties of undefined (reading 'CHECKOUT_STORE_KEY')

This error message, while a fairly common type of error in JavaScript, was particularly puzzling in this case because:

  1. The stack trace pointed to a non-existent line (likely due to Jest’s code transformation)
  2. The error originated from packages/checkout/index.js, a file that shouldn’t have been involved in the tests

The Investigation

The error occurred when testing code that imported from our new events module:

import {
	CHECKOUT_EVENTS,
	checkoutEventsEmitter,
} from '@woocommerce/blocks-checkout-events';

The next steps were to figure out why that package was being loaded. Nothing in my test was trying to load from @woocommerce/blocks-checkout.

Despite having properly configured the alias in jest.config.json, Jest was mysteriously loading from the checkout package instead of our events module.

The Root Cause

The issue stemmed from how Jest processes moduleNameMapper entries:

  • Entries are evaluated as regular expressions
  • Processing happens lazily and in order
  • Earlier matches take precedence over later ones

In our configuration, @woocommerce/blocks-checkout appeared before @woocommerce/blocks-checkout-events. Since the shorter path matched first, Jest never reached the more specific mapping.

The Solution

On a hunch, I decided to change the order of the imports so that @woocommerce/blocks-checkout-events was first. and it worked!

There were two possible fixes:

  1. Add an end anchor ($) to the shorter pattern: "@woocommerce/blocks-checkout$": "packages/checkout"
  2. Reorder the mappings to put the more specific pattern first:
"@woocommerce/blocks-checkout-events": "assets/js/events",
"@woocommerce/blocks-checkout": "packages/checkout",

Key Takeaways

  • Jest’s moduleNameMapper uses regular expressions, not simple string matching
  • Order matters in module mapping configurations
  • More specific patterns should either:
    • Come before more general patterns, or
    • Use precise regex anchors to prevent unintended matches
  • When debugging Jest configuration issues, pay attention to which modules are actually being loaded versus which ones you expect to be loaded

Additional Resources

Leave a Reply

Discover more from Thomas Roberts - JS Engineer

Subscribe now to keep reading and get access to the full archive.

Continue reading