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:
- The stack trace pointed to a non-existent line (likely due to Jest’s code transformation)
- 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:
- Add an end anchor (
$) to the shorter pattern:"@woocommerce/blocks-checkout$": "packages/checkout" - 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
moduleNameMapperuses 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
Leave a Reply