wallabyjs / public

Repository for Wallaby.js questions and issues
http://wallabyjs.com
760 stars 45 forks source link

Jest mocking and hoisting with Wallaby #2039

Closed madandrija closed 5 years ago

madandrija commented 5 years ago

Issue description or question

When starting Wallaby.js it doesn't run any tests and I am getting this output

0 failing tests, 0 passing    Error: Cannot find module 'src/config/persist.config' from 'redux.config.ts'  at Object. ​src/domain/dropdownAlertHelper/DropdownAlertHelper.tsx:4​ at Object. ​src/domain/createWorkout/createWorkoutRedux.ts:12​ at Object. ​src/test/reduxMockState.ts:3​ at Object. ​src/test/testsSetup.js:5​

Except the at Object. ​src/test/testsSetup.js:5​ line, all other lines are imports.

As far as I have figured it out, the problem is that I'm in testSetup.js mocking src/config/redux.config using reduxMockState, reduxMockState imports createWorkoutRedux which imports DropdownAlertHelper, which, on line 4, tries to import src/config/persist.config (the thing that I'm mocking), and since the unmocked version references ReactNativeRealm, the whole thing fails.

Now, I would consider this an issue with circular referencing (or something similar) if Jest CLI tests failed - but they pass without any issues.

I've found and tried some preprocessor/transformation options but they didn't work (perhaps I just didn't find correct ones).

Wallaby.js configuration file

module.exports = function(wallaby) {
  return {
    files: [
      "src/**/*.js",
      "src/**/*.ts",
      "src/**/*.tsx",
      "src/**/*.png",
      "src/**/*.jpg",
      "!src/**/*.test.tsx",
      "!src/**/*.test.ts",
      "!src/**/*.test.js",
      "!src/config/*.ts"
    ],

    tests: ["src/**/*.test.ts", "src/**/*.test.js", "src/**/*.test.tsx"],

    env: {
      type: "node",
      runner: "node"
    },
    testFramework: "jest",
    lowCoverageThreshold: 70 // 70%
  };
};

babel.config.js

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ["module:metro-react-native-babel-preset"]
  };
};

Jest config (in package.json)

"jest": {
    "preset": "react-native",
    "modulePaths": [
      "<rootDir>"
    ],
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
      "^.+\\.(ts|tsx)?$": "ts-jest"
    },
    "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(ts|tsx|js|jsx)?$",
    "testPathIgnorePatterns": [
      "\\.snap$",
      "<rootDir>/node_modules/",
      "<rootDir>/__tests__/",
      "<rootDir>/.history/"
    ],
    "transformIgnorePatterns": [
      "node_modules/?!(react-native|native-base)"
    ],
    "cacheDirectory": ".jest/cache",
    "setupFiles": [
      "<rootDir>/node_modules/appcenter/test/AppCenterMock.js",
      "<rootDir>/node_modules/appcenter-analytics/test/AppCenterAnalyticsMock.js",
      "<rootDir>/node_modules/appcenter-crashes/test/AppCenterCrashesMock.js"
    ],
    "globals": {
      "__TEST__": true
    },
    "setupTestFrameworkScriptFile": "<rootDir>/src/__test__/testsSetup.js"
  },

testsSetup.js

import "jest-extended";
import assetsList from "src/assets";
import setupLocalisation from "src/domain/localisation/setupLocalisation";
import { createStore } from "redux";
import { mockReducer } from "src/__test__/reduxMockState";
import { generateIDForRequest } from "src/domain/api/requestCreator";

jest.mock("src/config/redux.config", () => createStore(mockReducer));
jest.mock("src/domain/icons/icons", () => {
  return {
    getIcon: () => assetsList.crown,
    loadIcons: () => ["crown"]
  };
});
jest.mock("Alert", () => {
  return {
    alert: jest.fn()
  };
});
jest.mock("react-native-uuid-generator", () => {
  return {
    getRandomUUID(callback) {
      callback(generateIDForRequest());
    }
  };
});

Promise.resolve(setupLocalisation("en"));

const myModule = require("src/domain/api/requestCreator");
myModule.generateIDForRequest = jest.fn();
// in your test you can use this e.g. in beforeEach
/*
let value = 1;
(generateIDForRequest as jest.Mock).mockImplementation(() => (value++).toString());
*/

Code editor or IDE name and version

Visual Studio Code v1.32.3

OS name and version

OSX Mojave

smcenlly commented 5 years ago

Could you please try adding a preprocessor to your wallaby configuration file to hoist jest mocks to the top of your file (see below)?

module.exports = function(wallaby) {
  return {
    files: [
      "src/**/*.js",
      "src/**/*.ts",
      "src/**/*.tsx",
      "src/**/*.png",
      "src/**/*.jpg",
      "!src/**/*.test.tsx",
      "!src/**/*.test.ts",
      "!src/**/*.test.js",
      "!src/config/*.ts"
    ],

    tests: ["src/**/*.test.ts", "src/**/*.test.js", "src/**/*.test.tsx"],

    env: {
      type: "node",
      runner: "node"
    },
    testFramework: "jest",
    lowCoverageThreshold: 70, // 70%

    preprocessors: {
      '**/*.js?(x)': file =>
        require('@babel/core').transform(file.content, {
          sourceMap: true,
          compact: false,
          filename: file.path,
          presets: [require('babel-preset-jest')]
        })
    },
  };
};

While not a react-native app, you may find our create-react-app --TypeScript sample useful as well.

ArtemGovorov commented 5 years ago

Could you please also let us know what is the file extension of src/config/persist.config, is it ts, js, or tsx?

madandrija commented 5 years ago

Could you please try adding a preprocessor to your wallaby configuration file to hoist jest mocks to the top of your file (see below)?

Added, when using "**/*.js?(x)": file =>, first I get

[Error] Failed to run preprocessors on src/__test__/testsSetup.js, Error: babel-plugin-jest-hoist: The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables. 
[Error] Invalid variable access: createStore 
[Error] Whitelisted objects: Array, ArrayBuffer, Boolean, DataView, Date, Error, EvalError, Float32Array, Float64Array, Function, Generator, GeneratorFunction, Infinity, Int16Array, Int32Array, Int8Array, InternalError, Intl, JSON, Map, Math, NaN, Number, Object, Promise, Proxy, RangeError, ReferenceError, Reflect, RegExp, Set, String, Symbol, SyntaxError, TypeError, URIError, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray, WeakMap, WeakSet, arguments, console, expect, isNaN, jest, parseFloat, parseInt, require, undefined, DTRACE_NET_SERVER_CONNECTION, DTRACE_NET_STREAM_END, DTRACE_HTTP_SERVER_REQUEST, DTRACE_HTTP_SERVER_RESPONSE, DTRACE_HTTP_CLIENT_REQUEST, DTRACE_HTTP_CLIENT_RESPONSE, global, process, Buffer, clearImmediate, clearInterval, clearTimeout, setImmediate, setInterval, setTimeout, _, Q, EventEmitter, $_$tracer, $_$wv, $_$wf, $_$w. 
[Error] Note: This is a precaution to guard against uninitialized mock variables. If it is ensured that the mock is required lazily, variable names prefixed with `mock` (case insensitive) are permitted. 
[Error]     at invariant (~/src/MobileApp/node_modules/babel-plugin-jest-hoist/build/index.js:14:11) 
[Error]     at Object.FUNCTIONS.mock.args [as mock] (~/src/MobileApp/node_modules/babel-plugin-jest-hoist/build/index.js:113:9) 
[Error]     at shouldHoistExpression (~/src/MobileApp/node_modules/babel-plugin-jest-hoist/build/index.js:161:36) 
[Error]     at PluginPass.ExpressionStatement (~/src/MobileApp/node_modules/babel-plugin-jest-hoist/build/index.js:167:13) 
[Error]     at newFn (~/src/MobileApp/node_modules/@babel/traverse/lib/visitors.js:193:21) 
[Error]     at NodePath._call (~/src/MobileApp/node_modules/@babel/traverse/lib/path/context.js:53:20) 
[Error]     at NodePath.call (~/src/MobileApp/node_modules/@babel/traverse/lib/path/context.js:40:17) 
[Error]     at NodePath.visit (~/src/MobileApp/node_modules/@babel/traverse/lib/path/context.js:88:12) 
[Error]     at TraversalContext.visitQueue (~/src/MobileApp/node_modules/@babel/traverse/lib/context.js:118:16) 
[Error]     at TraversalContext.visitMultiple (~/src/MobileApp/node_modules/@babel/traverse/lib/context.js:85:17) 
[Error]     at TraversalContext.visit (~/src/MobileApp/node_modules/@babel/traverse/lib/context.js:144:19) 
[Error]     at Function.traverse.node (~/src/MobileApp/node_modules/@babel/traverse/lib/index.js:94:17) 
[Error]     at NodePath.visit (~/src/MobileApp/node_modules/@babel/traverse/lib/path/context.js:95:18) 
[Error]     at TraversalContext.visitQueue (~/src/MobileApp/node_modules/@babel/traverse/lib/context.js:118:16) 
[Error]     at TraversalContext.visitSingle (~/src/MobileApp/node_modules/@babel/traverse/lib/context.js:90:19) 
[Error]     at TraversalContext.visit (~/src/MobileApp/node_modules/@babel/traverse/lib/context.js:146:19) 
[Error]     at Function.traverse.node (~/src/MobileApp/node_modules/@babel/traverse/lib/index.js:94:17) 
[Error]     at traverse (~/src/MobileApp/node_modules/@babel/traverse/lib/index.js:76:12) 
[Error]     at transformFile (~/src/MobileApp/node_modules/@babel/core/lib/transformation/index.js:88:29) 
[Error]     at runSync (~/src/MobileApp/node_modules/@babel/core/lib/transformation/index.js:45:3) 
[Error]     at transformSync (~/src/MobileApp/node_modules/@babel/core/lib/transform.js:43:38) 
[Error]     at Object.transform (~/src/MobileApp/node_modules/@babel/core/lib/transform.js:22:38) 
[Error]     at **/*.js?(x) (~/src/MobileApp/wallaby.config.js:24:32) 
[Error]     at process._tickCallback (internal/process/next_tick.js:61:11) 

and then that changes to:


0 failing tests, 0 passing 
 
● Validation Error: 
 
  Module ./src/__test__/testsSetup.js in the setupTestFrameworkScriptFile option was not found. 
         <rootDir> is: ~/src/MobileApp 
 
  Configuration Documentation: 
  https://jestjs.io/docs/configuration.html 
 

Could you please also let us know what is the file extension of src/config/persist.config, is it ts, js, or tsx?

It's ts

smcenlly commented 5 years ago

There are various other github issues (non-Wallaby related) that reference the same error that you're receiving but we've not seen it before so need a sample repo to help further.

Can you please provide us with a sample repo that runs in jest and not wallaby. It should include your testsSetup.ts and a simple source/test file (doesn't need to have any of your application logic in them).

ArtemGovorov commented 5 years ago

It's ts

Thanks, I think I can explain the original issue now.

Error: Cannot find module 'src/config/persist.config' from 'redux.config.ts' 

src/config/persist.config.ts file is being excluded in your Wallaby config by:

  return {
    files: [
      ...
      "!src/config/*.ts"
    ],

Hopefully the issue should be resolved by removing the line:

  return {
    files: [
      ...
-     "!src/config/*.ts"
    ],
madandrija commented 5 years ago

It's ts

Thanks, I think I can explain the original issue now.

Error: Cannot find module 'src/config/persist.config' from 'redux.config.ts'

src/config/persist.config.ts file is being excluded in your Wallaby config by:

  return {
    files: [
      ...
      "!src/config/*.ts"
    ],

Hopefully the issue should be resolved by removing the line:

  return {
    files: [
      ...
-     "!src/config/*.ts"
    ],

Wow, that was it, I can't believe we didn't notice this - thank you very much!