testdouble.js
testdouble.js copied to clipboard
Testdouble third-party-test-thing with ESM module based tests using Mocha
Testdouble third-party-test-thing with ESM module based tests using Mocha
You can tag this as a Question :)
Scenario
During the course of converting an Opensource module from CJS to ESM I have discarded some tooling. Explicitly
- babel
- babel rewire
I have found that the babel rewire tool was very useful for custom mocks of third party modules.
Moving to a fully ESM based solution meant switching mocking tooling. After reading some of @giltayar postings I started to use Testdouble. It is by all means an excellent tool. However is there an assumption for the third-party-thing-test that when using ESM that the imported third party module is also ESM?
What I believe I am seeing is that for the is-number module frequently used in the testdouble documentation for the third-party-thing-test the case where an ESM module uses a CJS module the replace is not working as documented.
Am I seeing this correctly? If so is it worth me documenting (I'm volunteering) this scenario and saying that one solution is to wrap the third party solution in your own module and to mock that?
I am linking to an example of what I am attempting to describe.
For the example described I am using
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^9.1.4",
"testdouble": "^3.16.4"
},
"dependencies": {
"is-number": "^7.0.0"
}
while running node node 16.13.1
However is there an assumption for the third-party-thing-test that when using ESM that the imported third party module is also ESM?
Not really. You can use td.replace for CJS and td.replaceEsm for ESM. The slightly non-obvious thing is that you use td.replace for CJS even if you import it.
PR for saying that more explicitly in the documentation I believe would be welcome.
Sincere thanks for the response. I shall conduct some tests of my own and raise a PR to the documentation.
I did try as was suggested however in the scenario that I have linked even with the suggested modifications I'm not convinced that mocking a third party CJS module from a ESM module is working. I"ll update the readme in my example. I am happy to collaborate , update docs or help in any suggested way. If I am using testdouble incorrectly it may be a good case for documentation changes.
A reproduction repository would help me help you here!
The link to the repo above has the example https://github.com/ghinks/esm-issues-with-td
Nice one!
Fix the problem by adding the line
delete require.cache[require.resolve('is-number')]
before the await import('./index.js').
Explanation: this has nothing to do with ESM. You would have gotten the same result if you'd used CJS for all your files. The problem is specific to is-number, because is-number is used by Mocha as well (to prove it, run npm ls is-number). So when you import 'mocha' at the start of the test file, it's loading is-number into the cache, and thus overriding any attempts to td.replace it. The interesting thing is that if is-number was an ESM module, this would have worked, because ESM replacement is better than CJS replacement in this regard, as detailed in the documentation:
As a result, feel free to use td.replaceEsm even after you import the module. This is different from when using td.replace where you must use td.replace first before require-ing your module. (found in https://github.com/testdouble/testdouble.js/blob/main/docs/7-replacing-dependencies.md#how-module-replacement-works-for-es-modules-using-import)
Hope that was clear enough.
Fantastic a very clear explanation and received with gratitude 🙏🏻
@ghinks Two things:
-
I believe the testdouble explanations are deficient in that area (both for CJS and ESM), and I remember falling through the same trap numerous times. Maybe one day I'll tackle it in the documentation, probably through numerous examples.
-
If we're done, could you please close the issue? ☺️
Before I close this, please allow me a day to look at what was said and try to implement it. We are all in the ESM boat together and I want to understand and present some documentation changes so others do not have quite the same pain. I'm looking at the NodeJS documentation and while in ESM mode I'll not be able to access require.cache to clear things. So the strategy presented may work for this scenario while working with CJS but while working in ES Modules I"m not sure I see a mechanism to clear loaded modules. Hope, I"m not being too much of a pain. I think we both have the correct intent of clearing a path for future users of the testdouble module.
@ghinks. If the package is an ESM package, you won't have the problem: ESM proxying in testdouble handles modules that were already loaded with panache! But I agree the documentation should be clearer on this.
Actually the example is set within package.json as type module. It is not being handled correctly and as it is an ESM module I cannot clear the cache. I will push up more examples. But I believe that this is a real issue.
@ghinks the package is-number is a CJS package. That is not dependent on the package.json of the parent package.