Repository navigation
Using testdouble esm loader with ts-node/esm loader on mocha leads to errors (Node 18.6.0) #496
Description
Description
I am trying to do esm mocking using testdouble with a mixed Typescript and Javascript project, mostly following this post
Node 18.6.0 now allows for chaining of loads, and I previously needed ts-node/esm in order to recognize my Typescript files and imports.
I realize that this new feature is less than a week old, but I would appreciate support with reconciling other loaders that I would need for my es6 project (or to find a way that only requires one loader). Due to project requirements, using commonjs on runtime is not an option.
Issue
When running the following mocha command:
NODE_OPTIONS='--experimental-specifier-resolution=node --loader=ts-node/esm --loader=testdouble' mocha test/.js test/.spec.ts
and any permutation thereof (removing experimental-specifier-resolution, changing the order of the --loader params)
I get the following exception
Exception in PromiseRejectCallback:
node:internal/modules/esm/loader:178
return output;
RangeError: Maximum call stack size exceeded
Exception in PromiseRejectCallback:
/Users/repos/moRepo/node_modules/ts-node/dist/esm.js:120
const { source: rawSource } = await defaultLoad(url, {
^
RangeError: Maximum call stack size exceeded
at validateArgs (node:internal/modules/esm/loader:578:26)
These 4 lines repeat indefinitely:
at addShortCircuitFlag (/Users/repos/myRepo/node_modules/ts-node/src/esm.ts:409:21)
at load (/Users/repos/myRepo/node_modules/ts-node/src/esm.ts:239:12)
at nextLoad (node:internal/modules/esm/loader:173:28)
at /Users/repos/myRepo/node_modules/ts-node/src/esm.ts:255:45
Environment
node v18.6.0
npm v.8.13.2
testdouble v.3.16.6
Example Repo
I don't have a repo handy, but I can try to create one and add it in the future.
Code-fenced Examples
service_one.ts
export function getAllInvalidObjects(connection: Connection) {
const response: string[] = service_two.getData(someString);
... // some transformations of response
return response;
}
service_two.ts
// this is the function I'd like to mock when testing service_one.ts
export function getData(someString) {
axios.get('https://endpoint.com/path?string=' + someString).then((response: any) {
resolve(response.json);
}
}
test.ts :
import * as td from 'testdouble';
let mock_validation_rest: any;
beforeEach(async () => {
mock_rest = await td.replaceEsm('../src/services/service_two.js');
});
afterEach(function () {
sandbox.restore();
td.reset();
});
it('successfully validates with mocking, async () => {
td.when(mock_rest.prototype.getData(td.matchers.anything())).thenResolve(mockData);
// as long as the mock above works then this test will pass
return service_one.getAllInvalidObjects(connection).then((response: string[]) => {
assert.equal(3, response.length);
});
});
package.json:
{
"type": "module",
"scripts": {
"test": NODE_OPTIONS='--experimental-specifier-resolution=node --loader=ts-node/esm --loader=testdouble' mocha test/*.js test/*.spec.ts -r dotenv/config
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "es2016",
"module": "es6,
"moduleResolution": "node16"
"allowJs": true,
"esModuleInterop": true
},
"ts-node": {
"esm": true
}
"include": [
"./src/**/*",
"test/**/*/.ts",
"test/**/*.js"
}
}
Activity
searls commentedon Jul 22, 2022
Could you please also make sure
quibbleis up-to-date and share its version? Tagging @giltayar if he knows about any of thisgiltayar commentedon Jul 25, 2022
@AlexZ-343 would love to get a reproduction repository so that I can debug it this week! It would make my life much easier.
AlexZ-343 commentedon Jul 25, 2022
quibbleis 0.6.13, which is the latest release. Working on the reproduction repository and I will link it when ready.AlexZ-343 commentedon Jul 25, 2022
I have the repo set is private. I have added @searls and @giltayar to it.
https://github.com/AlexZ-343/testdouble-issue
giltayar commentedon Jul 26, 2022
Reproduced! Working on it...
giltayar commentedon Jul 26, 2022
Found the bug! Fixing...
AlexZ-343 commentedon Aug 2, 2022
Hi @giltayar, what's the status on the resolution? I see a fix has been fixed, but there hasn't been a release yet?
AlexZ-343 commentedon Aug 4, 2022
Checking in again @giltayar @searls
alichry commentedon Aug 6, 2022
Hi @AlexZ-343, I'm facing this issue as well. There's a fix by the node team in nodejs/node#44109. I am going to test it using node v19.0.0-nightly20220806760ecc9c75/ that includes it. if I find anything, I'll let you know.
alichry commentedon Aug 7, 2022
I've created a playground here: testdouble-ts-node-esm-playground. Mocking
fssucceeded, however, mocking internal modules written in typescript did not. The outcome is identical when using nodev18.7.0or nodev19.0.0-nightly20220806760ecc9c75giltayar commentedon Aug 8, 2022
@searls did you create a release?
searls commentedon Aug 8, 2022
@giltayar no, I'd actually invited you to after adding you as a maintainer on the npm package, along with an updated RELEASE.md doc!
Would you like to try your hand at releasing a patch bump for this?
AlexZ-343 commentedon Aug 9, 2022
hey @alichry, thanks for taking a look at this!
Could you clarify what you mean by this statement?
I'll try playing around with it a bit on my end as well.
AlexZ-343 commentedon Aug 9, 2022
@giltayar @searls let me know when you guys are able to make a release.
alichry commentedon Aug 10, 2022
Hi @AlexZ-343 👋, whoops, sorry about that.
internalis a bit ambiguous since internal modules often refer to internal nodejs modules. Nonetheless, I meant to imply that, when using thets-node/esmloader andtestdoubleloader, mocking ES modules written in TypeScript did not work.Based on the above, mocking
fssucceeded but not./ps.tshttps://github.com/alichry/testdouble-ts-node-esm-playground/blob/d3de64aafd29294b54f9140548a3904509b2b326/src/register.spec.ts#L7-L12
https://github.com/alichry/testdouble-ts-node-esm-playground/blob/d3de64aafd29294b54f9140548a3904509b2b326/src/memory.spec.ts#L7-L21
yarn test:jssucceeds.test:jstranspiles the source code and test files written in ts into ESM, and runs the *.spec.js test files. This is a convenient workaround.searls commentedon Aug 10, 2022
Try updating to quibble@0.6.14
alichry commentedon Aug 10, 2022
Hey @searls, thank you for the release! I've set it to 0.6.14 instead of the fix's commit hash. I've noticed no changes whatsoever; what am I missing?
searls commentedon Aug 10, 2022
Not sure, just wanted to make sure you were up to day. @giltayar?
AlexZ-343 commentedon Aug 10, 2022
Hey @searls and @giltayar , I tried the latest version of quibble with my dummy repo and while I am no longer seeing the lengthy exception message, I am having some trouble getting a proper mock.
When I try mocking with prototype, I get this:
If I try to do it without
prototype:It acts like there is no mock (which I confirmed with some console logging).
Is there an error in how I'm setting it up?
Updated dummy repo for reference: https://github.com/AlexZ-343/testdouble-issue
giltayar commentedon Aug 10, 2022
@AlexZ-343 looking into it.
giltayar commentedon Aug 10, 2022
@AlexZ-343 this was an interesting one. There are two problems in your test code. One which is obvious, and one which is definitely not. Let's start with the obvious:
You're calling
td.replaceEsm('../services/json-placeholder-rest.js')after theimport * as getPostTitles from '../get_post_service.js'. Soget_post_service.jsmodule has already imported the non-mocked version ofjson-placeholder-rest.js.To fix this, remove
import * as getPostTitles from '../get_post_service.js'from the top and replace it with agetPostTitles = await import('../get_post_service.js');after thetd.replaceEsm(...). This will ensure that whenget_post_service.jsimportsjson-placeholder-rest.jsit will import the mocked version.OK, that's the regular one. Now for the VERY non-obvious one, which got me stumped. Let's start by how to fix. Replace
td.replaceEsm('../services/json-placeholder-rest.js')with...td.replaceEsm('../services/json-placeholder-rest.ts')(i.e. replace.jsby.ts).Why? Because the
ts-node/esmloader replaces the module resolution of thejson-placeholder-rest.jsfile with the path that ends with.ts. And because thetestdoubleloader comes afterts-node/esm, then the import it receives is to the.tsfile, which it hasn't mocked! (because the mock was for the.jsfile)Totally non-obvious, but this is a really interesting interplay with multiple loaders here. We're probably going to see a lot fo those in the future, and now I will probably be able to figure them out without
console.log-ing stuff inquibble:-)AlexZ-343 commentedon Aug 10, 2022
@giltayar thanks for walking me through this.
I tried the fixes, and I still get:
This is with
.prototype(prototypeis correct, right? Without it, there's no mock at all).Is there something else I'm missing, like the exact ordering of the import or something?
I updated the repo with my attempted changes.
giltayar commentedon Aug 11, 2022
Oh, I fogort: the
prototypething I didn't understand and removed. Why would there be a prototype? It's just a function exported from a module.fix: to mock memory.ts, the path passed to replaceEsm
AlexZ-343 commentedon Aug 23, 2022
Hey all, sorry for the delay. I haven't had a chance to test the fix extensively.
I did try it for a unit test or two and it looks good! Personally, I'd like to do a few more test cases before closing this, but I think this should be fixed at this point.