Running tests located in a separate directory with mocha and ts-node? - node.js

I have source code and tests separated as follows:
`src/main/ts/hello.ts` //SOURCE FILES HERE
`src/test/ts/hello.spec.ts` //SPEC FILES HERE
The import statement in src/test/ts/hello.spec.ts looks like this:
import hello from 'hello';
The hello.ts source code looks like this:
export function hello() {
return 'Hello World!';
}
export default hello;
My tsconfig.json is setup such that the test files can import source modules without using relative paths like this:
{
"include": [
"src/main/ts/**/*.ts"
],
"exclude": [
"node_modules"
],
"compilerOptions": {
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "node",
"target": "es6",
"baseUrl": ".",
"paths": {
"*": [
"*", "src/main/ts/*"
]
}
}
}
This way the hello.spec.ts file can import hello using the statement import hello from 'hello';
I'm trying to run the tests with npm test configured to run mocha and tsnode like this (Based on this article):
"scripts": {
"test": "mocha -r ts-node/register src/test/ts"
},
However it does not look like ts-node is picking up on my tsconfig.json configuration as I get this error:
mocha -r ts-node/register src/test/ts
Error: Cannot find module 'hello'
at Function.Module._resolveFilename (module.js:336:15)
at Function.Module._load (module.js:286:25)

The module resolution that you set through paths in tsconfig.json is purely an compile-time thing. (See this ts-node issue report and this TypeScript issue report for details.) It does not affect how the code is emitted, which means that your test file is doing a require("hello"), which Node cannot resolve. The consequence of paths being a compile-time thing is that your module loader needs to be configured to also perform the same kind of resolution that you specify in tsconfig.json. If you were using RequireJS, for instance, you'd need to have a configuration for it that does the same thing paths in tsconfig.json does. You are using Node, however...
What you can do in Node is use tsconfig-paths, which will read the tsconfig.json, parse the paths setting and change the module resolution in Node so that it works.
Using your code, I modified hello.spec.ts to have at least one test for feedback:
import hello from "hello";
import "mocha";
it("q", () => {
if (hello() !== "Hello World!") {
throw new Error("unequal");
}
});
I installed tsconfig-paths and #types/mocha (so that import "mocha" does the right thing compilation-wise in the test file I show above) and invoked Mocha like this:
$ ./node_modules/.bin/mocha --compilers ts:ts-node/register -r tsconfig-paths/register 'src/test/ts/**/*.ts'
I got this output:
✓ q
1 passing (20ms)

Related

Jest Typescript with ES Module in node_modules error - Must use import to load ES Module:

I'm trying to write a simple jest test for a 3rd party package were using that only exports an ES module. It's a wrapper around an http server.
Here is a test repo I setup (just run yarn && yarn jest to reproduce): https://github.com/jamesopti/hocuspocus-testing
No matter what config I experiment with, I still get this error when trying to run it:
Must use import to load ES Module: /Users/j/hocuspocus-testing/node_modules/#hocuspocus/server/dist/hocuspocus-server.esm.js
> 1 | import { Server, Hocuspocus } from '#hocuspocus/server'
| ^
2 | import * as request from 'supertest'
3 |
4 | describe('Server (e2e)', () => {
Things I've tried already:
The Jest instructions on ES modules: https://jestjs.io/docs/ecmascript-modules
In Jest configuration using transformIgnorePatterns
transformIgnorePatterns: ['node_modules/(?!#hocuspocus/)']
Using Babel via babel-jest
modifying transform setup in Jest configuration as '^.+\.jsx?$': 'babel-jest', '^.+\.tsx?$': 'ts-jest'
Ran into the error You appear to be using a native ECMAScript module configuration file, which is only supported when running Babel asynchronously.
Using .babel.config.js instead of .babelrc.js
Any ideas what I'm missing here? I thought this would be straightforward
[EDIT 1] - Added tsconfig.json and a working src/index.ts file to the example repo.
So for anyone still hitting this, ESM configuration explained in this section of documentation :
https://kulshekhar.github.io/ts-jest/docs/guides/esm-support
{
// [...]
"jest": {
"extensionsToTreatAsEsm": [".ts"],
"globals": {
"ts-jest": {
"useESM": true
}
}
}
}
JAN 2023: ES2022, TypeScript 4.9.4, jest 29.3.1, ts-jest 29.0.3
This is what worked for me, after 2 hours of frustration.
I used this configuration in jest.config.ts:
import type { JestConfigWithTsJest } from 'ts-jest'
const config: JestConfigWithTsJest = {
extensionsToTreatAsEsm: ['.ts'],
verbose: true,
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
transform: {
'^.+\\.(ts|tsx)?$': ['ts-jest', { useESM: true }]
},
testPathIgnorePatterns: ['./dist']
}
export default config
Change the test script in package.json to:
I use pnpm. Change to npx jest with npm,
or yarn exec with yarn
...
"scripts": {
...
"test": "NODE_OPTIONS=--experimental-vm-modules pnpm exec jest",
...
}
...
tsconfig.json:
{
"compilerOptions": {
"rootDirs": ["src"],
"outDir": "dist",
"lib": ["ES2022"],
"target": "ES2022",
"module": "ES2022",
"composite": true,
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"incremental": true,
"esModuleInterop": true,
"types": ["jest", "node", "#types/jest"],
"sourceMap": true
},
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
},
"include": ["./src/**/*", "./tests/**/*"]
}
See this (rather confusing) documentation for reference:
https://kulshekhar.github.io/ts-jest/docs/guides/esm-support/
You don't have a tsconfig.json file that specifies module. Therefore it uses the default, where it transpiles all your modules to CommonJS syntax, which uses require.
If you actually look at your dist/hocuspocus-server.esm.js, you should see it using require over the ESM import syntax.
I was having the same problem with my svelte app and testing. I ultimately traced it to having a jest.config.js and a jest.config.json in my root folder. It seems that jest does not have automatic config file resolution and was using a default configuration instead of either of my specified configurations.

How to pass separate TypeScript config for TSNode when it has being indecency running by Mocha & IntellIJ IDEA?

Although TS-Node started the ECMAScript modules support, it has a lot of limitations and below example:
import { assert as Assert } from "chai";
describe("firstTest", (): void => {
it("", (): void => {
Assert.isTrue(true);
});
});
does not work with next settings:
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "node",
"strict": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"baseUrl": "./",
"paths": {},
"noUnusedParameters": true,
"noImplicitReturns": true
}
}
.mocharc.yaml
extension:
- ts
spec: "**/*.test.ts"
require:
- ts-node/register
- tsconfig-paths/register
Error:
import { assert as Assert } from "chai";
^^^^^^
SyntaxError: Cannot use import statement outside a module
I need the ECMAScript modules because the Webpack dynamic loading does not work with CommonJS modules. So the conceptual solution is create the additional tsconfig.json for TSNode or pass modules type via console. It's has been documented how to do it for TSNode, but here I don't execute TSNode directly - I launch the Mocha via IntelliJ IDEA:
IntelliJ IDEA and, I suppose, all IDEs of this family including WebStorm generates the below command (added the line breaks):
"C:\Program Files\nodejs\node.exe"
"D:\XXX\MyProject\node_modules\mocha\bin\mocha" --require ts-node/register --ui bdd --reporter C:\Users\XXX\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\203.7148.57\plugins\NodeJS\js\mocha-intellij\lib\mochaIntellijReporter.js
"D:\XXX\MyProject\Test\Test.test.ts" --grep "^firstTest "
Now how to set CommonJS modules type?
According to ts-node's documentation (search for IntelliJ), you can create an alternative tsconfig.json (containing the option "module": "CommonJS") and enforce it for Mocha tests using environment variable TS_NODE_PROJECT. In the configuration corresponding to your Mocha testing, in the Environment variables section, you set this variable to the pathname of this alternative tsconfig.json. Note, multiple tsconfig.json files can use one common "base" file and extend/partially override it (see).

cannot run compiled typescript prog using module resolution

I'm trying to compule the following typscript file
import { magic } from 'lib/magic';
magic();
The filestructure is:
./src/
main.ts
lib/
a/magic.ts
b/magic.ts
Inside tsconfig.json I map lib/magic to the right file as follows
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"outDir": "./dist",
"baseUrl": ".",
"paths": {
"lib/*": [ "src/lib/a/*" ]
}
},
"include": [ "./src/**/*.ts" ],
"exclude": [ "./node_modules" ]
}
It is that paths section that maps that import lib/magic to ./src/lib/a/magic.ts.
So, I can compile as follows
$> tsc -p ./tsconfig-a.json
It produces output in dist. However, when I try to run it
$> node ./dist/main.js
internal/modules/cjs/loader.js:626
throw err;
^
Error: Cannot find module 'lib/magic'
Require stack:
...
It makes sense, because in dist there is no such thing as lib/magic. Any suggestions how to fix this?
Until someone proves me wrong, here are my findings so far.
Here is an issue in which it is stated that all this is in fact expected behaviour.
So you basically have to fix it in a second step. In my case I want to make 2 build, one for a and one for b. The easiest way to fix it then is to have it working by default for a as follows
import { magic } from './lib/a/magic';
magic();
Then build b and replace that path as follows
sed -e 's/lib\/a/lib\/b/' ./dist/main.js > ./dist/main-node.js
In my case, one version was for NodeJs and the other for the browser, so I also needed to add the .js extension. Just in case anyone is interested, here is the command
$> echo "import { magic } from './lib/a/magic';" | sed "s#^\(import[^']*\)'\([^']*\)#\1 '\2.js#g"
Please let me know if you have a better solution, because all this feels a bit hacky

Jest gives an error: "SyntaxError: Unexpected token export"

I'm using Jest to test my React app.
Recently, I added DeckGL to my app. My tests fail with this error:
Test suite failed to run
/my_project/node_modules/deck.gl/src/react/index.js:21
export {default as DeckGL} from './deckgl';
^^^^^^
SyntaxError: Unexpected token export
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:318:17)
at Object.<anonymous> (node_modules/deck.gl/dist/react/deckgl.js:9:14)
at Object.<anonymous> (node_modules/deck.gl/dist/react/index.js:7:15)
This looks like an issue with Jest transforming a node module before running it's tests.
Here is my .babelrc:
{
"presets": ["react", "es2015", "stage-1"]
}
Here is my jest setup:
"jest": {
"testURL": "http://localhost",
"setupFiles": [
"./test/jestsetup.js"
],
"snapshotSerializers": [
"<rootDir>/node_modules/enzyme-to-json/serializer"
],
"moduleDirectories": [
"node_modules",
"/src"
],
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/test/EmptyModule.js"
}
},
I seem to have the correct things necessary to transform export {default as DeckGL }. So any ideas whats going wrong?
This means, that a file is not transformed through TypeScript compiler, e.g. because it is a JS file with TS syntax, or it is published to npm as uncompiled source files. Here's what you can do.
Adjust your transformIgnorePatterns allowed list:
{
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!#ngrx|(?!deck.gl)|ng-dynamic)"
]
}
}
By default Jest doesn't transform node_modules, because they should be valid JavaScript files. However, it happens that library authors assume that you'll compile their sources. So you have to tell this to Jest explicitly. Above snippet means that #ngrx, deck and ng-dynamic will be transformed, even though they're node_modules.
And if you are using 'create-react-app', it won't allow you to specify 'transformIgnorePatterns' via Jest property in package.json
As per this https://github.com/facebook/create-react-app/issues/2537#issuecomment-390341713
You can use CLI as below in your package.json to override and it works :
"scripts": {
"test": "react-scripts test --transformIgnorePatterns \"node_modules/(?!your-module-name)/\"",
},
This is because Node.js cannot handle ES6 modules.
You should transform your modules to CommonJS therefore.
Babel 7 >=
Install
npm install --save-dev #babel/plugin-transform-modules-commonjs
And to use only for test cases add to .babelrc,
Jest automatically gives NODE_ENV=test global variable.
"env": {
"test": {
"plugins": ["#babel/plugin-transform-modules-commonjs"]
}
}
Babel 6 >=
npm install --save-dev babel-plugin-transform-es2015-modules-commonjs
to .babelrc
"env": {
"test": {
"plugins": ["transform-es2015-modules-commonjs"]
}
}
Jest by default won't compile files in the node_modules directory.
transformIgnorePatterns [array]
Default: ["/node_modules/"]
An array of regexp pattern strings that are matched against all source
file paths before transformation. If the test path matches any of the
patterns, it will not be transformed.Default: ["/node_modules/"]
DeckGL seems to be in ES6, to make jest able to read it, you need to compile this as well.
To do that, just add an exception for DeckGL in the transformignorePatterns
"transformIgnorePatterns": ["/node_modules/(?!deck\.gl)"]
https://facebook.github.io/jest/docs/en/configuration.html#transformignorepatterns-array-string
I was having this issue with a monorepo. A package in the root node_modules was breaking my tests. I fixed by changing my local .babelrc file to babel.config.js. Explanation: https://github.com/facebook/jest/issues/6053#issuecomment-383632515
It was work around #1 on this page that fixed it for me though workaround #2 on that page is mentioned in above answers so they may also be valid.
"Specify the entry for the commonjs version of the corresponding package in the moduleNameMapper configuration"
jest.config.js
moduleNameMapper: {
"^uuid$": require.resolve("uuid"),
"^jsonpath-plus$": require.resolve("jsonpath-plus")
...
In my case I use this config in the file package.json:
"jest": {
"transformIgnorePatterns": [
"!node_modules/"
]
}
This code worked for me
// .babelrc
{
"presets": [
["env", {
"modules": "commonjs", // <- Check and see if you have this line
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"],
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
}
}
}
jest understands commonJs so it needs babel to transform the code for it before use. Also jest uses caching when running code. So make sure you run jest --clearCache before running jest.
Tested Environment:
Node v8.13.0
Babel v6+
Jest v27
I'm using a monorepo (it contains multiple packages for the frontend and backend).
The package I'm testing imports a file from another package that uses the dependency uuid.
All the files are in Typescript (not Javascript).
The package I'm testing has a tsconfig file for testing only, called tsconfig.test.json. It has the properties commonjs and allowJs. Adding allowJs solves the problem when importing uuid, I don't know why.
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": [
"jest",
"node"
],
// Necessary to import dependency uuid using CommonJS
"allowJs": true
},
"include": [
"jest.config.ts",
"**/*.test.ts",
"**/*.d.ts"
]
}
I was upgrading a project that uses a version of babel that reads the config from .babelrc, when I upgraded to a newer version I read:
https://babeljs.io/docs/en/configuration#whats-your-use-case
What's your use case?
You are using a monorepo?
You want to compile node_modules?
babel.config.json is for you!
On top of:
{
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!(module))"
]
}
}
I renamed .babelrc to babel.config.json too.
I had the same error of importing dataSet from vis-data.js library
import { DataSet } from 'vis-data/esnext';
So I just removed /esnext from the path and now it works:
import { DataSet } from 'vis-data';

TypeScript AMD compilation and "barrel" modules

I'm trying to set up a Node.js + TypeScript project using Intern for testing. Everything works fine when I compile the project using "commonjs" (which I do for the normal build); and TypeScript is equally happy when compiling for "amd", which is required by Intern. However, when passing the tests with intern-client, it complains about a couple of things:
First, imports from "index.ts" files (so-called "barrel" modules) won't work. My setup is something like this (everything in the same directory):
// index.ts
export { x } from './x'
// x.ts
export function x() {}
// x.test.ts
import { x } from '.' // "Error: Failed to load module ..."
In fact, the generated JavaScript code (for x.test.ts) looks something like this:
define(["require", "exports", "."], function (...) { ... })
And I'm not sure that AMD knows how to handle the ".".
The second issue happens under the same circumstances (TypeScript compiles happily, but intern-client complains). In summary, I get an error when doing:
import jsdom = require('jsdom')
Which I need to transform to:
const jsdom = require('jsdom')
For Intern to be able to deal with it.
Here is the tsconfig.json file I use to compile the tests:
{
"compilerOptions": {
"target": "es6",
"module": "amd",
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "src",
"outDir": "build/tests",
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
}
}
And here is my intern.js configuration file, in case it helps:
define({
suites: ['build/tests/**/*.test.js'],
excludeInstrumentation: true,
filterErrorStack: true
})
Edit (2017-05-03)
To help understand the issue, here is an excerpt of the directory tree of the project:
build
tests // The compiled tests will end up here
src
core
utils
x.ts
x.test.ts
// Other files, each containing a function that I would like to unit-test...
intern.js
package.json
tsconfig.json
...
Regarding the first issue, AMD's handling of an import like '.' is different than Node's. While both of them will map '.' to a package, Node uses a default module name of index.js, while AMD uses main.js. To get things working in an AMD loader, you'll need to first define a package for '.', and then tell the AMD loader what default module to use for that package. Given your project layout, you could configure Intern like this:
loaderOptions: {
map: {
// When a module in src/ references 'src/utils', redirect
// it to 'utils'
'src': {
'src/utils': 'utils'
}
},
packages: [
// Define a package 'utils' with files in 'src/utils' that defaults
// to the module index.js
{ name: 'utils', location: 'src/utils', main: 'index.js' }
]
}
Regarding the second issue, its not clear what the problem actually is. Import statements will be transpiled into define dependencies by TypeScript, so Intern should never be seeing them.

Resources