jest.mock() 不模拟 Babel 7 和 React-Native 0.56 中的模块

jest.mock() not mocking module in Babel 7 and React-Native 0.56

像许多其他人一样,我正在尝试将我的 RN 应用程序升级到 React-Native 0.56,Babel 让一切变得如此简单! (这里不是讽刺)我可以更新 RN,React 到最新版本并使我的应用程序编译和工作,但是测试(开玩笑)不再工作了。

但不是所有的测试。我设法解决了几乎所有不同类型的问题,期待这个:

meeting actions › creates CLEAR_CURRENT_ATTACHMENTS when clearning current attachments

TypeError: eventActions.refreshEvents.mockImplementation is not a function

  90 |
  91 |   beforeEach(() => {
> 92 |     eventActions.refreshEvents.mockImplementation(() => ({ type: DUMMY_TYPE }));
     |                                ^
  93 |     MockDate.set(A_DATE);
  94 |     store = mockStore({
  95 |       authentication: {

  at Object.<anonymous> (src/calendar/actions/__tests__/meetingActions-test.js:92:32)

测试失败的测试文件(删除所有其他测试以简化示例)

import moment from 'moment';
import MockDate from 'mockdate';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import getToken from 'authentication/selectors/tokenSelector';
import * as actions from '../meetingActions';
import * as eventActions from '../eventActions';
import { getPlannerComments } from '../../selectors/scheduleModificationSelectors';

const {
  CLEAR_CURRENT_ATTACHMENTS,
} = actions;

jest.mock('../eventActions.js');
jest.mock('../../../authentication/selectors/tokenSelector');
jest.mock('../../selectors/scheduleModificationSelectors');

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('meeting actions', () => {
  const A_TOKEN = 'token12345';
  const AN_ID = 'h1234';
  const A_DATE = moment('2016-08-22T21:00:00.000Z');
  const AN_MEETING_EVENT = {
    start: A_DATE,
    end: A_DATE,
    guests: [],
    newGuests: [],
    newAttachments: [],
    contactsToAddToGuestList: [],
    groupsToAddToGuestList: [],
    listsToAddToGuestList: [],
  };
  const AN_ACCOUNT = { id: AN_ID };
  const DUMMY_TYPE = 'Dummy type';
  const PLANNER_COMMENTS = {};
  let store;

  beforeEach(() => {
    eventActions.refreshEvents.mockImplementation(() => ({ type: DUMMY_TYPE }));
    MockDate.set(A_DATE);
    store = mockStore({
      authentication: {
        token: A_TOKEN,
      },
      session: {
        account: AN_ACCOUNT,
      },
      meeting: AN_MEETING_EVENT,
      event: {
        attendees: [],
      },
    });
    getToken.mockImplementation(() => A_TOKEN);
    getPlannerComments.mockImplementation(() => PLANNER_COMMENTS);
  });

  it('creates CLEAR_CURRENT_ATTACHMENTS when clearing current info', () =>
    store.dispatch(actions.clearCurrentInfo())
      .then(() => {
        expect(store.getActions()).toContainEqual({ type: CLEAR_CURRENT_ATTACHMENTS });
      }));

  afterEach(() => {
    MockDate.reset();
  });
});

我有超过 800 个测试因为这个错误而失败。根据我对问题的理解,它来自 jest.mock

jest.mock('../domain/Attachment') // For example

实际上并不是在嘲笑class。所以附件看起来像正常的 class。因此,函数的 mockImplementation 会导致 undefined is not a function

{ [Function: Record]
  iconOf: [Function: iconOf],
  iconColorOf: [Function: iconColorOf],
  getCleanName: [Function: getCleanName],
  open: [Function: _callee2],
  parse: [Function: parse] }

Package.json

{
  "name": "MY APP",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest",
    "ios": "react-native run-ios",
    "android": "react-native run-android",
    "test:watch": "npm run test -- --watch",
    "lint": "eslint src --max-warnings=0",
    "install-dep": "npm install && cd ios && pod install"
  },
  "rnpm": {
    "assets": [
      "./src/main/themes/fonts/assets"
    ]
  },
  "jest": {
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "transform": {
      "^.+\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
    },
    "preset": "react-native",
    "collectCoverage": true,
    "setupTestFrameworkScriptFile": "<rootDir>/setup-jasmine-env.js",
    "moduleFileExtensions": [
      "js",
      "json",
      "es6",
      "ios.js",
      "android.js"
    ],
    "setupFiles": [
      "./testenv.js"
    ],
    "transformIgnorePatterns": [
      "node_modules/?!(react-native)"
    ]
  },
  "dependencies": {
    "bluebird": "^3.4.6",
    "buffer": "^5.0.0",
    "color": "^2.0.0",
    "deepmerge": "^1.5.2",
    "diacritics": "^1.2.3",
    "immutable": "^3.8.2",
    "linkify-it": "^2.0.3",
    "lodash.debounce": "^4.0.8",
    "moment": "^2.17.0",
    "prop-types": "^15.6.0",
    "react": "16.4.1",
    "react-native": "0.56.0",
    [...] // Lots of dependencies not related to the problem
    "react-navigation": "1.5.11",
    "react-redux": "^5.0.7",
    "redux": "^3.7.2",
    "redux-thunk": "^2.2.0",
    "reselect": "^3.0.1",
    "socket.io-client": "2.1.0"
  },
  "devDependencies": {
    "@babel/core": "^7.0.0",
    "babel-core": "^6.26.3",
    "babel-eslint": "7.2.3",
    "babel-jest": "^23.4.2",
    "babel-plugin-module-resolver": "^3.1.1",
    "babel-preset-react-native": "5.0.2",
    "eslint": "^3.11.1",
    "eslint-config-airbnb": "^13.0.0",
    "eslint-import-resolver-babel-module": "5.0.0-beta.0",
    "eslint-plugin-import": "^2.12.0",
    "eslint-plugin-jsx-a11y": "^2.2.3",
    "eslint-plugin-react": "^6.7.1",
    "eslint-plugin-react-native": "^2.2.0",
    "fetch-mock": "^5.1.2",
    "istanbul": "0.4.5",
    "jasmine-reporters": "^2.2.0",
    "jest": "^23.5.0",
    "jest-cli": "23.5.0",
    "mockdate": "^2.0.1",
    "moment-timezone": "^0.5.20",
    "react-dom": "16.4.2",
    "react-test-renderer": "16.4.2",
    "redux-mock-store": "1.3.0",
    "regenerator-runtime": "^0.12.1",
    "remote-redux-devtools": "^0.5.7"
  }
}

请注意,就像 Jest、Babel 和 React-Native GitHubs 中的多个问题一样,我有转换。

"transform": {
   "^.+\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
},

.babelrc

{
  "presets": ["react-native"],
  "plugins": [
    [
      "module-resolver",
      {
        "root": ["./src"],
        "extensions": [".js", ".ios.js", ".android.js"]
      }
    ]
  ]
}

有人知道吗?

如果您查看示例 here,您会发现 mockImplementation 只能在 jest.fn() 对象上调用。在您的情况下,您需要先模拟 refreshEvents 然后在该模拟上调用 mockImplementation

...
eventActions.refreshEvents = jest.fn();
eventActions.refreshEvents.mockImplementation(() => ({ type: DUMMY_TYPE }));
...

如果您想保留您发布的代码,那么您必须在您的模拟模块旁边创建一个子目录 __mock__,并创建一个模拟该模块中函数的文件在该子目录中,如 here.

所述

更新:

jest.mock 调用会使用 babel-jest 转换器自动提升到文件顶部,这正是您所需要的。请注意,您已经在使用 jest 转换器。来自 docs:

If you are using the babel-jest transformer and want to use an additional code preprocessor, keep in mind that when "transform" is overwritten in any way the babel-jest is not loaded automatically anymore. If you want to use it to compile JavaScript code it has to be explicitly defined.

这意味着您必须在 package.json 的 jest 配置中明确包含 babel-jest 转换。那应该可以解决问题。