-
Notifications
You must be signed in to change notification settings - Fork 284
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
How to run the Node built-in testrunner for TypeScript files inside a specific directory? #3902
Comments
Did you try injecting a TypeScript processor as a command line parameter for E.g. |
In case it helps anyone who runs across this issue, here is how I currently execute tests written in TypeScript with the new node test runner and esbuild-kit/tsx: npm install -D tsx Add the following npm script entry: "test": "node --loader tsx --test test/**/*Test.ts" Let me know if this is helpful. Edit: See the reply below by @scottwillmoore for an excellent tip to make this work more robustly across platforms. |
@neodon I tried to reproduce your approach but I get a "not found" error ( I'm using Node 19 ) tsconfig.json {
"compilerOptions": {
"baseUrl": ".",
"declaration": true,
"esModuleInterop": true,
"lib": ["es2022", "dom"],
"module": "commonjs",
"outDir": "build",
"resolveJsonModule": true,
"strict": true,
"target": "es2022"
},
"include": ["./**/*.ts"],
"exclude": ["./build"]
} package.json {
"scripts": {
"test": "tsc --noEmit && node --loader tsx --test test/**/*Tests.ts"
},
"dependencies": {
"@types/node": "18.11.9"
},
"devDependencies": {
"tsx": "3.11.0",
"typescript": "4.8.4"
}
} ./test/someTests.ts import assert from 'assert/strict';
import { describe, it } from 'node:test';
describe('tests', () => {
it('passes', () => {
assert.ok(true);
});
}); The test script fails with
Did I miss something? |
EDIT:See this example repository which I have created. ProblemI haven't tested your environment, but if I had to guess I believe it is your
On a POSIX system SolutionI could be wrong, but I believe since Better SolutionIt would be better to rewrite the script in a cross-platform manner, instead of relying on platform dependent features in your scripts which is may be unreliable. This way your script should even work on Windows. There might be an easier way to leverage another package, but I think I would just move it into a JavaScript or TypeScript script. Change your script to:
Then create a file named I don't have the time to experiment with a full script, but I expect you would want to use the RelatedWhy you should always quote your globs in NPM scripts. Follow UpI originally wrote this answer of StackOverflow, but perhaps someone here has an easier solution than writing your own script. Also, I hope I've actually diagnosed the issue, otherwise that would be a little awkward 😬 . |
Another option to run the same command on Windows as well is to use globstar: {
"scripts": {
"test": "globstar -- node --loader tsx --test \"test/**/*.spec.ts\""
}
} (Last release of this package seems to be 8 years ago, but still does the job.) |
Did some general fixer-uppers to the codebase! Now it's more ES6-friendly in some spots, it consistently uses double-quotes now, and part of the ES6-friendly bits, now arrow functions are used a bit more, and any var declaration that's modified once is now a const declaration :) Forgot to include some links that helped out for the last few commits: nodejs/help#3902 https://stackoverflow.com/questions/35470511/setting-up-tsconfig-with-spec-test-folder https://jestjs.io/docs/ecmascript-modules https://khalilstemmler.com/blogs/typescript/abstract-class/ (Going to look into if this is what MediaFileReader should instead be made with. Haven't used this before!) https://stackoverflow.com/questions/45251664/derive-union-type-from-tuple-array-values (THIS IS FREAKING EPIC, big thanks to this post)
@scottwillmoore I was trying out your example and in Node v20, it now breaks with:
The simplest solution I found was to add |
You're right! This does not happen in Node 19 with The solution, as indicated with @Ethan-Arrowood is to just add I've also double checked, and a custom script is still required to run TypeScript with the Node test runner at the moment. This is due to the current Node test runner execution model. However, there is a feature request to fix this issue: nodejs/node#46292. |
Hmm when I try
|
it seems to be caused by https://github.com/nodejs/node/blob/8becacb25d3c275341a9fef1a94581de3bd60c3d/src/env-inl.h#L638-L641 @nodejs/loaders @nodejs/workers do workers require a v8 inspector in order to work? |
This sounds familiar I think it ended up being a red-herring. |
A potential solution to the OP is to pre-compile TS to JS. Then you don't need a loader and with more than two unit tests it becomes a lot faster too, since you compile everything only once and no further overhead. (example commit that does this using swc). |
Are you sure? I think it was a red herring for that PR in that Node’s internal test runner doesn’t use |
Weird. I have Windows 11 Pro (Build 22621) and in PowerShell with Node 20 installed with Volta I don't have any issues. git clone [email protected]:scottwillmoore/node-test-with-typescript
cd ./node-test-with-typescript
volta --version # 1.1.1
node --version # v20.0.0
npm --version # 9.6.4
npm clean-install
# Indirect
npm run test # node --loader tsx --no-warnings ./scripts/test.ts
# Direct
node --loader tsx --no-warnings --test ./tests/add.test.ts Could @MoLow try with this example, or make their example reproducible. |
@GeoffreyBooth is v8 inspector a requirement for using workers? |
Well I think most users would assume that they can attach the debugger to worker threads, so when we run v8 for a worker it expects to have that feature enabled? |
https://github.com/isaacs/node-glob recently got cli capabilities, so this is what I do now
|
Awesome, thanks for the heads up. I've updated my example to use this approach. You can use the command below if you don't want to require Yarn. "test": "glob -c \"node --loader tsx --no-warnings --test\" \"./tests/**/*.test.ts\"" |
Hi, following back on this, is it possible to pass flags to tsx when using it as a node loader? I need to specify a different tsconfig for testing. Thanks! |
Also another solution for those for non-windows users who don't want to install additional dependencies: "test": "find ./src -name '*.spec.ts' -exec node --loader @swc-node/register/esm --test {} \\;" |
* `npm install --save-dev @tsconfig/strictest @tsconfig/node18` * Update tsconfig with using tsconfig/base again * `npm install --save-dev tsx` * `npm uninstall ts-node-test` * Use glob to run tsx with test runner for TS files nodejs/help#3902 (comment) * Replace ts-node-test with tsx * Update docs around repl
I've followed this and I get |
This sent me in the right direction but I wanted to pass all the files to the command instead of one by one. So here's what worked for me: "test": "find ./test -name '*Tests.ts' | tr '\\n' ' ' | xargs node --require ts-node/register --test " |
Just seen above #3902 (comment) that the fix for the syntax issue is to set “type”:”module” in package.json Is there a workaround for those of us who can’t do that? This is a big project, and most of the files in this project are still commonjs and that can’t really change due to other reasons (upstream electron not supporting ESM right now). is there not a way to have tsx output commonjs instead? Or to use .mjs if it’s insistent on using esm. |
The whole CJS and ESM migration is kind of ugly at the moment... As mentioned in my previous comment, I did investigate this problem, but never found a good answer. I don't have the time at the moment to continue looking into this specific issue, but I'll leave some notes below which might help someone more motivated. At the moment Node has two module loaders. There is the CommonJS module loader and the ECMAScript module loader. You can read how Node determines which module loader to use in the documentation. To load an unsupported file type such as TypeScript the module loader has to be overridden. However, the method used to achieve this for each module loader is quite different.
Now, the
So, there are many factors which determine what may happen in your case.
It will depend on the module loader determined by Node. It will depend on how Both
Anyway, so I don't know if that makes much sense. This comment is far longer than I anticipated. I don't know how accurate my description of this process is, but the overall process is clearly complicated and hard to understand and fix. It was at this point in the past I kind of gave up. I don't know your exact problem, but I suspect you could try to use |
Thanks @scottwillmoore In case anyone else comes here, I've managed to get it working by doing It seems like the underlying issue is a Node v20 breaking change If I run |
Giving that this costed me hours of time I will share my finding. Easy setup:
Using custom import { readdirSync } from 'node:fs'
import { run } from 'node:test'
import process from 'node:process'
import { spec as Spec } from 'node:test/reporters'
import { basename } from 'node:path'
import { cpus } from 'node:os'
const spec = new Spec()
let exitCode = 0
const files = readdirSync(__dirname)
.filter((f) => f.endsWith('.ts') && f !== basename(__filename))
.map((f) => `${__dirname}/${f}`)
const start = Date.now()
const testStream = run({
files,
timeout: 60 * 1000,
concurrency: cpus().length,
})
testStream.compose(spec).pipe(process.stdout)
const summary: string[] = []
testStream.on('test:fail', (data) => {
exitCode = 1
const error = data.details.error
summary.push(
`${data.file} - "${data.name}" (${Math.round(
data.details.duration_ms,
)}ms)\n${error.toString()} `,
)
})
testStream.on('test:stderr', (data) => {
summary.push(`${data.file} - Error:\n${data.message} `)
})
testStream.once('end', () => {
const duration = Date.now() - start
// print duration in blue
console.log(
'\x1b[34m%s\x1b[0m',
`\nℹ Duration: ${duration / 1000}s\n`,
'\x1b[0m',
)
if (summary.length > 0) {
console.error('\x1b[31m%s\x1b', '\n✖ failing tests:\n')
console.error(summary.join('\n'))
console.error('\n------', '\x1b[0m\n')
}
process.exit(exitCode)
}) The pro of using |
Quick note that @robertsLando's example does not work on NodeJS |
@JustinGrote could you explain me what specifically doesn't work with my example in nodejs 18? I have nodejs 18 and 20 in my ci and they both work |
@robertsLando |
I really tried but without any luck. Finally resorted to import { run } from "node:test";
import { spec } from "node:test/reporters";
import process from "node:process";
import { glob } from "glob";
const tsTests = await glob("src/**/*.test.ts");
run({ files: tsTests }).compose(new spec()).pipe(process.stdout);
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false
}
}
} and {
// ...
"type": "module",
"scripts": {
// ...
"test": "SWCRC=true node --loader @swc-node/register/esm run-tests.ts"
// ...
}
// ...
} Was it worth the effort? IDK. Update:
|
After a lot of trial and error I got this to work on Windows:
|
For run all test files in you project by "node" on typescript, run command bellow: find -name "*test.ts" | xargs node --loader tsx --test If it works, add it to scripts in I tried to run all tests with |
@Dias1c pretty sure that's why people are using glob, it's cross platform :) |
For new Node versions. The This worked for me: |
You can just use
The real misunderstanding seems to be how default globbing works with nodejs. If you're using
It will run fine. But if you use bash the shell it will fail. I assume the default globbing in nodejs uses the same backwards compatible syntax in linux where So to run all tests under
|
Hey guys, is anyone having issues with environment variables not being defined with node test runner? I am running my test with this command |
Currently you can even shorten it to (on "test": "glob -c \"tsx --test\" \"./src/**/*.test.ts\"" |
I just made a succinct guide to tie all of the learnings mentioned here together, including If you want a TL;DR, check this Gist instead. Everything is free and I make no money from them either, it was just easier to share the info there for whomever may be interested :) Node >=22.9.0 (if you want optionality for the "test": "glob -c \"tsx --env-file-if-exists .env --env-file-if-exists .env.test --test --test-reporter spec \" \"./test/**/*.test.ts\"", Node < 22.9.0 (throws error if the "test": "glob -c \"tsx --env-file .env --env-file .env.test --test --test-reporter spec \" \"./test/**/*.test.ts\"", |
node: "we now support typescript and built in tests" |
It absolutely is possible (unless I've misunderstood you): #!/usr/bin/env -S tsx
import test = require('node:test');
import setupTests = require('./setupTests');
type NodeTestRunnerParameters = Required<Parameters<typeof test.run>>;
type NodeTestRunnerOptions = NodeTestRunnerParameters[0];
type NodeTestRunnerReturnType = ReturnType<typeof test.run>;
const run = (proc: NodeJS.Process, options?: NodeTestRunnerOptions) => {
proc.on('unhandledRejection', (error) => {
throw error;
});
// ...
return test.run(options)
}
if (require.main === module) {
const testStream = runTests(global.process, setupTests);
} // setupTests.ts
import test = require('node:test');
import reporters = require('node:test/reporters');
import path = require('node:path');
import os = require('node:os');
type NodeTestRunnerParameters = Required<Parameters<typeof test.run>>;
type NodeTestRunnerOptions = NodeTestRunnerParameters[0];
const options: NodeTestRunnerOptions = {
// use either 'files' or 'testNamePatterns' - not both...
files: [
path.resolve(path.join(__dirname, '/path/to/someModuleA.test.ts')),
path.resolve(path.join(__dirname, '/path/to/someModuleB.test.ts')),
],
// testNamePatterns: [
// "**/*.test.?(c|m)js",
// "**/*-test.?(c|m)js",
// "**/*_test.?(c|m)js",
// "**/test-*.?(c|m)js",
// "**/test.?(c|m)js",
// "**/test/**/*.?(c|m)js"
// ],
concurrency: os.availableParallelism() - 1,
setup(testsStream) {
// Log test failures to console
testsStream.on('test:fail', (testFail) => {
console.error(testFail);
process.exitCode = 1; // must be != 0, to avoid false positives in CI pipelines
});
// coverage reporter
const isTTY = process.stdout.isTTY;
const reporter = isTTY ? reporters.spec : reporters.tap;
testsStream.compose(reporter).pipe(process.stdout);
},
// ...
} satisfies NodeTestRunnerOptions; chmod +x ./runTests.ts
# then...
./runTests.ts or yarn tsx ./runTests.ts Apart from using tsx, there are other ways. But as it stands, tsx is the way to go for testing Typescript-based NodeJs projects, IMO. |
Details
I want to replace Mocha tests with the built-in testrunner. All tests are inside a test directory and follow the pattern
I started with a fooTests.ts file
and added the npm script
"test": "node --test ./test/**/*Tests.ts",
The script fails with the message
How can I fix the script to
./test/**/*Tests.ts
only? Thanks in advance!
Node.js version
v18
Operating system
Linux ( Ubuntu 22.04 )
Scope
Built-in testrunner
Module and version
Not applicable.
The text was updated successfully, but these errors were encountered: