As a newcomer to using jest with ts-jest, I am facing an error that I can't quite comprehend:
$ jest
FAIL src/lib/Core/Storage/JsonStorage.test.ts
● Test suite failed to run
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Here's what you can do:
• If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
• If you are trying to use TypeScript, see https://jestjs.io/docs/getting-started#using-typescript
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/configuration
For information about custom transformations, see:
https://jestjs.io/docs/code-transformation
Details:
/app/src/lib/Core/Storage/JsonStorage.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import fs from 'fs/promises'
^^^^^^
SyntaxError: Cannot use import statement outside a module
1 | import fs from 'fs/promises'
> 2 | import JsonStorage from './JsonStorage'
| ^
3 | import type IStorage from './IStorage'
4 |
5 | jest.mock('fs/promises')
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1495:14)
at Object.<anonymous> (src/lib/Core/Storage/JsonStorage.test.ts:2:1)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.235 s
Ran all test suites.
Presented below is the test I intend to carry out:
import fs from 'fs/promises'
import JsonStorage from './JsonStorage'
import type IStorage from './IStorage'
jest.mock('fs/promises')
describe('JsonStorage', () => {
let storage: IStorage<object>
beforeEach(() => {
storage = new JsonStorage('')
jest.resetAllMocks()
})
test('Storage json object to file', async () => {
fs.writeFile = jest.fn()
const data = {
one: 'one',
two: 'two',
}
const pathname = './mokefile.json'
const json = '{"one":"one","two":"two"}'
await storage.save(pathname, data)
expect(fs.writeFile).toHaveBeenCalled()
expect(fs.writeFile).toHaveBeenCalledWith(pathname, json, 'utf8')
})
})
Below is the code I am testing:
import fs from 'fs/promises'
import type IStorage from './IStorage'
export default class JsonStorage<T = object> implements IStorage<T> {
constructor(private _space: string) {}
async save(pathname: string, data: T) {
const json = JSON.stringify(data, null, this._space)
await fs.writeFile(pathname, json, 'utf8')
}
}
My tsconfig:
{
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"module": "es2022",
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@App/*": ["./src/lib/*"],
"@config/*": ["./src/config/*"]
},
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"alwaysStrict": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noPropertyAccessFromIndexSignature": false,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"useUnknownInCatchVariables": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"watchOptions": {
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",
"fallbackPolling": "dynamicPriority",
"synchronousWatchDirectory": true,
"excludeDirectories": ["node_modules"]
},
"include": ["src", "bin", "index.ts"],
"exclude": ["node_modules", "./**/*.test.ts"]
}
My jest.config.ts:
import { type JestConfigWithTsJest, pathsToModuleNameMapper } from 'ts-jest'
import { compilerOptions } from './tsconfig.json'
type ModuleNameMapper = Required<JestConfigWithTsJest>['moduleNameMapper']
const moduleNameMapper = pathsToModuleNameMapper(compilerOptions.paths) as ModuleNameMapper
const jestConfig: JestConfigWithTsJest = {
testEnvironment: 'node',
preset: 'ts-jest/presets/default-esm',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
isolatedModules: true,
tsconfig: 'tsconfig.jest.json',
},
],
},
transformIgnorePatterns: [
'^.+\\.jsx?$',
],
modulePaths: [compilerOptions.baseUrl],
moduleNameMapper,
}
export default jestConfig
I am aware that tsc
compiles files to the same location as the .ts files, leading to an error term -
/app/src/lib/Core/Storage/JsonStorage.js:1
, suggesting that it is imported into the test js file instead of the necessary TypeScript file. It seems that jest is not recognizing the ts files, and I am looking for a way to direct it to use the ts version without the need for babel:
module.exports = {
// other config options...
transform: {
"^.+\\.tsx?$": "ts-jest",
// add this line:
"^.+\\.jsx?$": "babel-jest"
}
};
I prefer not to involve babel and need to work with my ts files. How can I instruct jest to use the ts files instead of js?