iambumblehead / esmock

ESM import and globals mocking for unit tests
ISC License
197 stars 18 forks source link

`invalid moduleId` error when using tsx #209

Closed tommy-mitchell closed 1 year ago

tommy-mitchell commented 1 year ago

I'm trying to use esmock with AVA and TypeScript via transpilation with tsx (i.e. esbuild) on Node.js v18.16.1. My loader chain is effectively

node --loader=tsx --loader=esmock
AVA configuration ```json "files": [ "test/*.ts" ], "extensions": { "ts": "module" }, "nodeArguments": [ "--loader=tsx", "--loader=esmock", "--no-warnings=ExperimentalWarning" ] ```
Project structure ``` \__ src/ \__ source-file.ts \__ test/ \__ test-file.ts \__ package.json \__ tsconfig.json ```

When trying to mock my source file, I get an invalid moduleId error:

// test/test-file.ts

import test from "ava";
import esmock from "esmock";

test("with mock", async t => {
    const { setExecutableBits } = await esmock("../src/source-file.js", import.meta.url, {
        // ...
    }) as typeof import("../src/source-file.js");

    // ...
});
Rejected promise returned by test. Reason:

  Error {
    message: 'invalid moduleId: "../src/source-file.js" (used by file:///.../test/test-file.ts)',
  }

  › Object.$e (node_modules/esmock/src/esmock.js:1:61)
  › wt (node_modules/esmock/src/esmock.js:2:6113)
  › <anonymous> (node_modules/esmock/src/esmock.js:3:161)

Am I missing something?

iambumblehead commented 1 year ago

There's not an esmock tsx test and it would be great to have one. Feel free to submit a PR, otherwise I'll respond in a week or so with a solution.

related,

iambumblehead commented 1 year ago

it would be awesome if you would submit a PR with a failing test

tommy-mitchell commented 1 year ago

Added failing tests at #210.

iambumblehead commented 1 year ago

@tommy-mitchell thank you for the test cases. Possibly related, ava is not fully compatible with esm --loader

Will take a look later this evening

iambumblehead commented 1 year ago

this seems to come from a configuration issue, such as the configurations discussed here https://stackoverflow.com/questions/62096269/cant-run-my-node-js-typescript-project-typeerror-err-unknown-file-extension

the error occurs at this line, where importing the ts file causes the error

const importedModule = await import(fileURLKey)
//   TypeError {
//     code: 'ERR_UNKNOWN_FILE_EXTENSION',
//     message: 'Unknown file extension ".ts" for /home/bumble/software/esmock/tests/local/main.ts',

I'm not great at setting up configurations for things like this but will keep trying a little bit

iambumblehead commented 1 year ago

PR is here https://github.com/iambumblehead/esmock/pull/211

iambumblehead commented 1 year ago

this issue seems related https://github.com/avajs/ava/issues/2593#issuecomment-1524846453

tommy-mitchell commented 1 year ago

Potentially related too: https://github.com/avajs/ava/discussions/3213#discussioncomment-6315510

iambumblehead commented 1 year ago

these results indicate the problem may come from tsx,

I'll push up some changes to the PR that make it a little easier to switch between the two transpilers and test-runners

iambumblehead commented 1 year ago

At the PR, I removed the invalid moduleId test, because the moduleId was actually invalid (the file path being imported did not exist) and esmock is expected to throw an error in that situation. That is separate from the unknown-file-extension-error which seems to be a real issue.

iambumblehead commented 1 year ago

@tommy-mitchell I think I've narrowed down the issue to something that could be used to submit an issue at the tsx project,

test('tsx should not break when import paths include query params', async () => {
  assert.ok(await import('../local/main.ts?n=4'))
})

when query params are removed, the above test passes

iambumblehead commented 1 year ago

related issue is opened here https://github.com/esbuild-kit/tsx/issues/264

tommy-mitchell commented 1 year ago

Nice find.

iambumblehead commented 1 year ago

Closing with this https://github.com/iambumblehead/esmock/pull/211

A tsx note is embedded to the README scripts example. Feel free to reopen for any reason.

tonisives commented 1 year ago

I get the same invalid moduleId error for node --loader=ts-node/esm src/simpletest.test.ts

import { test } from "uvu"
import esmock from "esmock"
import { myFun } from "./simpletest.js"

test("test not real", async () => {
  let mock = await esmock("./simpletest.js", {
    myFun: () => console.log("mocked")
  })

  myFun()
})

test.run()
export const myFun = () => {
  console.log("still real");
}
➜ node --loader=ts-node/esm src/simpletest.test.ts
(node:5062) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
✘   (0 / 1)

   FAIL  "test not real"
    invalid moduleId: "myFun" (used by file:///Users/tonis/workspace/js/audit-hero/parsers/packages/findings/src/simpletest.test.ts)

uvu runner "ts-node": "^10.9.1", node 20.6.1 "uvu": "^0.5.6"

iambumblehead commented 1 year ago

@tonisives respectfully, the example you gave probably does not use esmock correctly. Call esmock to mock values that are imported by the target module. See this diff, for example, which results in a passing test using the two files attached further below.

diff --git a/src/simpletest.test.ts b/src/simpletest.test.ts
index a1633c6..13a3370 100644
--- a/src/simpletest.test.ts
+++ b/src/simpletest.test.ts
@@ -1,13 +1,14 @@
 import { test } from "uvu"
 import esmock from "esmock"
-import { myFun } from "./simpletest.js"
+import simpletest from "./simpletest.js"

 test("test not real", async () => {
   let mock = await esmock("./simpletest.js", {
-    myFun: () => console.log("mocked")
+    "./simplefunctions.js": { myFun: () => console.log("mocked") }
   })
-  
-  myFun()
+
+  mock()
+  simpletest()
 })

 test.run()

src/simplefunctions.ts

export const myFun = () => {
  console.log("still real");
}

src/simpletest.ts

import { myFun } from './simplefunctions.js'

export default myFun