Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using testdouble esm loader with ts-node/esm loader on mocha leads to errors (Node 18.6.0) #496

Closed
AlexZ-343 opened this issue Jul 20, 2022 · 24 comments

Comments

@AlexZ-343
Copy link

AlexZ-343 commented Jul 20, 2022

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"
      }
    }
@searls
Copy link
Member

searls commented Jul 22, 2022

Could you please also make sure quibble is up-to-date and share its version? Tagging @giltayar if he knows about any of this

@giltayar
Copy link
Collaborator

giltayar commented 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
Copy link
Author

quibble is 0.6.13, which is the latest release. Working on the reproduction repository and I will link it when ready.

@AlexZ-343
Copy link
Author

I have the repo set is private. I have added @searls and @giltayar to it.

https://github.com/AlexZ-343/testdouble-issue

@giltayar
Copy link
Collaborator

Reproduced! Working on it...

@giltayar
Copy link
Collaborator

Found the bug! Fixing...

@AlexZ-343
Copy link
Author

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
Copy link
Author

Checking in again @giltayar @searls

@alichry
Copy link

alichry commented 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
Copy link

alichry commented Aug 7, 2022

I've created a playground here: testdouble-ts-node-esm-playground. Mocking fs succeeded, however, mocking internal modules written in typescript did not. The outcome is identical when using node v18.7.0 or node v19.0.0-nightly20220806760ecc9c75

@giltayar
Copy link
Collaborator

giltayar commented Aug 8, 2022

@searls did you create a release?

@searls
Copy link
Member

searls commented 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
Copy link
Author

I've created a playground here: testdouble-ts-node-esm-playground. Mocking fs succeeded, however, mocking internal modules written in typescript did not. The outcome is identical when using node v18.7.0 or node v19.0.0-nightly20220806760ecc9c75

hey @alichry, thanks for taking a look at this!

Could you clarify what you mean by this statement?

mocking internal modules written in typescript did not

I'll try playing around with it a bit on my end as well.

@AlexZ-343
Copy link
Author

@giltayar @searls let me know when you guys are able to make a release.

@alichry
Copy link

alichry commented Aug 10, 2022

Could you clarify what you mean by this statement?

Hi @AlexZ-343 👋, whoops, sorry about that. internal is a bit ambiguous since internal modules often refer to internal nodejs modules. Nonetheless, I meant to imply that, when using the ts-node/esm loader and testdouble loader, mocking ES modules written in TypeScript did not work.

$ yarn test:ts
yarn run v1.22.18
warning You are using Node "19.0.0-nightly20220806760ecc9c75" which is not supported and may encounter bugs or unexpected behavior. Yarn supports the following semver range: "^4.8.0 || ^5.7.0 || ^6.2.2 || >=8.0.0"
$ NODE_OPTIONS='--loader=ts-node/esm --loader=testdouble' mocha src/**/*.spec.ts
(node:30884) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:30884) DeprecationWarning: Obsolete loader hook(s) supplied and will be ignored: getFormat, getSource


  Memory
    1) Should mock ps

  Register
    ✔ Should mock fs


  1 passing (66ms)
  1 failing

  1) Memory
       Should mock ps:

      AssertionError [ERR_ASSERTION]: 10000 == 700
      + expected - actual

      -10000
      +700
      
      at Context.<anonymous> (file:///private/tmp/quibble-ts-node-test/src/memory.spec.ts:21:16)
      at Generator.next (<anonymous>)
      at fulfilled (file:///private/tmp/quibble-ts-node-test/src/memory.spec.ts:4:58)



error Command failed with exit code 1.

Based on the above, mocking fs succeeded but not ./ps.ts

https://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:js succeeds. test:js transpiles the source code and test files written in ts into ESM, and runs the *.spec.js test files. This is a convenient workaround.

@searls
Copy link
Member

searls commented Aug 10, 2022

Try updating to quibble@0.6.14

@alichry
Copy link

alichry commented 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
Copy link
Member

searls commented Aug 10, 2022

Not sure, just wanted to make sure you were up to day. @giltayar?

@AlexZ-343
Copy link
Author

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:

td.when(mock_json_placeholder_rest.prototype.getPostById(td.matchers.anything())).thenResolve(response);

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

If I try to do it without prototype:

td.when(mock_json_placeholder_rest.getPostById(td.matchers.anything())).thenResolve(response);

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
Copy link
Collaborator

@AlexZ-343 looking into it.

@giltayar
Copy link
Collaborator

giltayar commented 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 the import * as getPostTitles from '../get_post_service.js'. So get_post_service.js module has already imported the non-mocked version of json-placeholder-rest.js.

To fix this, remove import * as getPostTitles from '../get_post_service.js' from the top and replace it with a getPostTitles = await import('../get_post_service.js'); after the td.replaceEsm(...). This will ensure that when get_post_service.js imports json-placeholder-rest.js it 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 .js by .ts).

Why? Because the ts-node/esm loader replaces the module resolution of the json-placeholder-rest.js file with the path that ends with .ts. And because the testdouble loader comes after ts-node/esm, then the import it receives is to the .ts file, which it hasn't mocked! (because the mock was for the .js file)

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 in quibble :-)

@AlexZ-343
Copy link
Author

@giltayar thanks for walking me through this.

I tried the fixes, and I still get:

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

This is with .prototype (prototype is 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
Copy link
Collaborator

Oh, I fogort: the prototype thing I didn't understand and removed. Why would there be a prototype? It's just a function exported from a module.

alichry added a commit to alichry/testdouble-ts-node-esm-playground that referenced this issue Aug 11, 2022
@AlexZ-343
Copy link
Author

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.

@searls searls closed this as completed Aug 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants