I'm using ts-jest to test a JS/TS SDK/module. I'm running into a strange issue where a Jest test will run (no compile/import failures) but fail to correctly instantiate an object from the appropriate class.
test("Should build unit", () => {
const builder = new UnitBuilder("TEST_UNIT");
console.log(builder); // prints "{}"
const unit = builder
.addResource(...)
.build();
expect(unit.name).toBe("TEST_UNIT");
});
The test fails with: TypeError: builder.addResource is not a function since the instantiated object is empty. Here's the class in question:
export class UnitBuilder {
constructor(templateId: string) {
this.payload = {
templateId,
parameters: [],
};
}
public addResource = (resource: CreateResourcePayload) => {
// do stuff
};
public build = () => {
// do stuff
};
public payload: CreateUnitPayload;
}
I'm assuming this has something to do with the jest or ts-jest transpilation, e.g. babel, but perhaps it's something to do with the jest configuration as well?
jest.config.ts
import type { Config } from "#jest/types";
const config: Config.InitialOptions = {
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json"],
verbose: true,
automock: true,
testMatch: ["**/__tests/*.test.ts"],
roots: ["<rootDir>/src"],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
};
export default config;
Removing automock: true fixed the problem.
Related
I have a React Native app in which I installed and used jail-monkey to check if the device is rooted. As soon as I added it, some of my Jest tests started failing with the following error:
SyntaxError: Cannot use import statement outside a module
> 3 | import JailMonkey from 'jail-monkey';
After googling I came upon this stack overflow thread which has many answers but neither of which helped me. That being said I imagine this problem has to do with the babel and jest configs - How to resolve "Cannot use import statement outside a module" in jest
My babel.config.js looks like this:
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
[
require.resolve('babel-plugin-module-resolver'),
{
cwd: 'babelrc',
extensions: ['.ts', '.tsx', '.ios.tsx', '.android.tsx', '.js'],
alias: {
'#src': './src',
},
},
],
[
'module:react-native-dotenv',
{
moduleName: 'react-native-dotenv',
},
],
// Reanimated needs to be at the bottom of the list
'react-native-reanimated/plugin',
],
};
And my jest.config.js looks like this:
const { defaults: tsjPreset } = require('ts-jest/presets');
/** #type {import('#jest/types').Config.InitialOptions} */
module.exports = {
...tsjPreset,
preset: 'react-native',
transform: {
'^.+\\.jsx$': 'babel-jest',
},
// Lists all react-native dependencies
// that don't have compiled ES6 code
// and need to be ignored by the transformer
transformIgnorePatterns: [
'node_modules/(?!(react-native' +
'|react-navigation-tabs' +
'|react-native-splash-screen' +
'|react-native-screens' +
'|react-native-reanimated' +
'|#react-native' +
'|react-native-vector-icons' +
'|react-native-webview' +
')/)',
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
moduleNameMapper: {
// Help Jest map the #src's added by babel transform
'^#src(.*)$': '<rootDir>/src$1',
// Allow Jest to mock static asset imports
'\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/assetMock.js',
// Mock SVG Component imports (from React Native SVG)
'\\.svg': '<rootDir>/__mocks__/svgMock.js',
},
setupFiles: ['./jest.setup.js'],
setupFilesAfterEnv: ['#testing-library/jest-native/extend-expect'],
};
I solved this issue by including jail-monkey in my transformIgnorePatterns on jest.config.js and them mocking the jailmonkey.js. In my case I have the file on __mocks__/jail-monkey/index.js with the following content:
export default {
jailBrokenMessage: () => '',
isJailBroken: () => false,
androidRootedDetectionMethods: {
rootBeer: {
detectRootManagementApps: false,
detectPotentiallyDangerousApps: false,
checkForSuBinary: false,
checkForDangerousProps: false,
checkForRWPaths: false,
detectTestKeys: false,
checkSuExists: false,
checkForRootNative: false,
checkForMagiskBinary: false,
},
jailMonkey: false,
},
hookDetected: () => false,
canMockLocation: () => false,
trustFall: () => false,
isOnExternalStorage: () => false,
isDebuggedMode: () => Promise.resolve(false),
isDevelopmentSettingsMode: () => Promise.resolve(false),
AdbEnabled: () => false,
};
So my problem is that since I implemented the p-retry lib (retry call api X times you want). On the localhost:3000 work fine but when I launch the tests I got the following return:
● 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:
/project/node_modules/p-retry/index.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import retry from 'retry';
^^^^^^
SyntaxError: Cannot use import statement outside a module
1 | import fetch from 'node-fetch';
> 2 | import pRetry, { AbortError } from 'p-retry';
| ^
3 |
4 | import HttpsProxyAgent from 'https-proxy-agent';
5 | const proxyAgent = process.env.HTTPS_PROXY
at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
at Object.<anonymous> (services/medVir/http.ts:2:1)
So I guess it's probably an error of config so this is my jest.config.js :
const nextJest = require('next/jest');
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env.local files in your test environment
dir: './',
});
// Add any custom config to be passed to Jest
const customJestConfig = {
clearMocks: true,
collectCoverage: true,
coverageDirectory: 'coverage',
coveragePathIgnorePatterns: [
'/node_modules/',
'__tests__/utils/',
'/public/',
],
moduleNameMapper: {
'\\.(css|less)$': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'identity-obj-proxy',
'^-!svg-react-loader.*$': '<rootDir>/config/jest/svgImportMock.js',
},
testEnvironment: 'jsdom',
testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
'**/?(*.)+(spec|test).[tj]s?(x)',
],
testPathIgnorePatterns: ['/node_modules/', '__tests__/utils/'],
// transformIgnorePatterns: ['node_modules/(?!(p-retry)/)'],
verbose: true,
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': [
'babel-jest',
{
presets: [
[
'#babel/preset-env',
{
targets: {
node: 'current',
},
},
],
'#babel/preset-typescript',
'#babel/preset-react',
],
},
],
},
setupFiles: ['<rootDir>/.jest/setEnvVars.js'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
// module.exports = customJestConfig;
module.exports = createJestConfig(customJestConfig);
I tried a lot of different config and implementation but nothing to do... still the same error so I`m wondering if the problem could be something else.
Something sure since I change axios to node-fetch with p-retry (to handle request and retry then) my tests just stoped to work
I come to give you a solution for people which crossing the same problem as me. So I didn't fix the lib or found jest config to handle the weird behavior so I made my own function to do kind of the same thing :
Which is recalling X times the same call api if the timeout is reach
code :
const makeAPICall = async ({
url,
body = '',
method = 'GET',
type = 'TEXT',
}: IApi) => {
// init path
const path = new URL(url);
const timeout = 28_000;
let myInit = {
method,
timeout,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
if (method !== 'GET') myInit = { ...myInit, ...{ body: body } };
const res = await fetch(path.href, myInit);
switch (type) {
case 'TEXT':
return res.text();
default:
return res.json();
}
};
const sleep = (ms: number) =>
new Promise((resolve) => setTimeout(() => resolve(), ms));
const makeApiRetry: any = async (args: IArgs, retries = 3, old_n = 0) => {
let n = old_n;
return makeAPICall(args).catch(async () => {
if (n < retries) {
n++;
// console.log('Retrying request', n, `waiting ${1000 * n} `, args.url);
await sleep(1000 * (n + 1));
return makeApiRetry(args, retries, n);
} else {
return Promise.reject('Too many retries : error timeout');
}
});
};
// Get node-fectch
export const getApiRoute = async (body: string) =>
await makeApiRetry({
url: `/apiRoute`,
body,
method: 'GET',
type: 'JSON',
});
I'm trying to mock module generating uuid that is used inside the function that I test, and for some reason jest.mock fails to mock it.
file structure
- __test__
-- testFunc.test.ts
- uuidGenerator.ts
- testFunc.ts
./testFunc.ts
import uuidGenerator from "./uuidGenerator";
export const getUUID = () => {
return uuidGenerator().generateUUID();
};
./uuidGenerator.ts
export default function uuidGenerator() {
return { generateUUID: () => "generated-uuid" };
}
./__tests__/testFunc.test.ts
import { getUUID } from "../testFunc";
const testVal = "test-uuid";
jest.mock("../uuidGenerator", () =>
jest.fn(() => ({
generateUUID: () => testVal,
}))
);
describe("test func", function () {
it("should return expected", function () {
expect(getUUID()).toBe(testVal);
});
});
test output:
Error: expect(received).toBe(expected) // Object.is equality
Expected: "test-uuid"
Received: "generated-uuid"
console.log(uuidGenerator) from testFunc.ts returns:
[Function: uuidGenerator]
Edit: I'm using jest 25.5 and here is the configuration file:
module.exports = {
testRegex: "/__tests__/.*(\\.test.js|\\test.jsx|\\.test.ts|\\.test.tsx)$",
testResultsProcessor: "./node_modules/jest-html-reporter",
setupFilesAfterEnv: ["<rootDir>test-setup.js"],
moduleFileExtensions: [
"ts",
"tsx",
"ios.ts",
"android.ts",
"web.ts",
"ios.tsx",
"android.tsx",
"web.tsx",
"js",
"json",
"jsx",
"web.js",
"ios.js",
"android.js",
"ejs",
],
snapshotSerializers: ["enzyme-to-json/serializer"],
modulePaths: ["<rootDir>/packages", "<rootDir>/plugins", "<rootDir>/scripts"],
modulePathIgnorePatterns: ["<rootDir>/xxx/"],
collectCoverageFrom: ["packages/**/*.js", "plugins/**/*.js"],
coveragePathIgnorePatterns: [
"__tests__",
"__mocks__",
"node_modules",
"test_helpers",
"flow-types.js",
],
transformIgnorePatterns: [
"node_modules/(?!(react-native|react-native-webview|react-native-status-bar-height|react-router-native/)"
],
transform: {
"^.+\\.(js|ts|tsx)$": require.resolve("react-native/jest/preprocessor.js"),
"^.+\\.ejs$": "<rootDir>/tools/ejs-transformer.js",
},
testEnvironment: "node",
preset: "react-native",
verbose: true,
watchPlugins: [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname",
],
};
Looks like the module is not getting mocked at all. Can anyone understand why is it happening?
Which version of Yarn are you using ? I just copy-pasted your code and run the test using Jest 26 and everything was working as expected.
Maybe you could try clearing the Jest cache folder : https://jestjs.io/docs/cli#--clearcache
I'm trying to mock the react-native-image-crop-picker package (the openCamera function specifically) in Detox but I'm not managing to do it. Detox simply does not consider the mock.
.detoxrc.json:
{
"testRunner": "jest",
"runnerConfig": "e2e/config.json",
"uiHierarchy": "enabled",
"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "yarn detox:prepare",
"type": "android.emulator",
"device": {
"avdName": "Pixel_4_API_29"
}
}
}
}
.config.json:
{
"preset": "ts-jest",
"testEnvironment": "./environment",
"testRunner": "jest-circus/runner",
"testTimeout": 120000,
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true,
"testMatch": [
"**/__tests__/**/*.js?(x)",
"**/?(*.)(e2e).js?(x)",
"**/__tests__/**/*.ts?(x)",
"**/?(*.)(e2e).ts?(x)"
]
}
config.js:
require('#babel/register')({
//cache: true,
presets: [require('metro-react-native-babel-preset')],
plugins: [require('#babel/plugin-transform-runtime').default],
only: ['./e2e', './ts', './js'],
ignore: ['node_modules'],
});
environment.js:
const {
DetoxCircusEnvironment,
SpecReporter,
WorkerAssignReporter,
} = require('detox/runners/jest-circus');
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
constructor(config) {
super(config);
// Can be safely removed, if you are content with the default value (=300000ms)
this.initTimeout = 300000;
// This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level.
// This is strictly optional.
this.registerListeners({
SpecReporter,
WorkerAssignReporter,
});
}
}
module.exports = CustomDetoxEnvironment;
metro.config.js:
const blacklist = require('metro-config/src/defaults/blacklist');
const defaultSourceExts = require('metro-config/src/defaults/defaults')
.sourceExts;
/**
* Metro configuration for React Native
* https://github.com/facebook/react-native
*
* #format
*/
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
/**
*
* Blacklist is a function that takes an array of regexes
* and combines them with the default blacklist to return a single regex.
*
*/
resolver: {
blacklistRE: blacklist([/.\/amplify\/.*/]),
sourceExts: process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
: defaultSourceExts,
},
};
E2E folder:
E2E Folder
ImageCropPicker.e2e.js:
import ImageCropPicker from 'react-native-image-crop-picker';
ImageCropPicker.openCamera = function openCamera() {
console.tron.log('mocked');
return {
mime: 'test',
data: 'test',
};
};
I also tried to put the ImageCropPicker.e2e.js file outside the mocks folder, but it did not work as well.
Detox, Node, Device and OS versions:
Detox: 17.6.0
Node: 10.23.0
Device: Pixel 4 API 29
OS: Linux Pop!_OS 20.10
React-Native: 0.62.0
Can you help me?
I appreciate your time!
I'm trying to use Typescript decorators in one project of mine, but I encountered an strange behaviour that I'm not capable of understand.
It only seems to work if the decorated class is inside the same method that is trying to obtain the metadata:
describe('metadata tests', () => {
it('should retrieve metadata', () => {
class Test {
#TestDecorator('some value')
property: string;
}
const metadata = Reflect.getMetadata('test', new Test(), 'property');
expect(metadata).toEqual('some value'); //Passes
});
});
But as soon as I move it outside the method, it does not work anymore:
describe('metadata tests', () => {
class Test {
#TestDecorator('some value')
property: string;
}
it('should retrieve metadata', () => {
const metadata = Reflect.getMetadata('test', new Test(), 'property');
expect(metadata).toEqual('some value'); //Fails
});
});
Both tests are using this test decorator:
function TestDecorator(value: any) {
return function (target: any, propertyKey: string) {
console.log(`I'm being decorated!`);
Reflect.defineMetadata('test', value, target, propertyKey);
};
}
And both are printing to the console...
Also, on both transpiled code I can see the property being decorated correctly and exactly in the same way:
var Test = (function () {
function Test() {
}
__decorate([
TestDecorator('some value'),
__metadata("design:type", String)
], Test.prototype, "property", void 0);
return Test;
}());
And here it is my tsconfig.json. I believe is correct (es5, emitDecoratorMetadata and experimentalDecorators):
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"declaration": true,
"outDir": "dist",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": true
},
"exclude": [
"node_modules",
"dist"
]
}
What am I missing?
For those with the same issue, I don't think this is a solution but, in my case, switching from Webpack to Rollup.js solved the problem...