使用 Jest 部分模拟 React 模块。

12

我正在尝试模拟导入的React模块中的一个函数,保持模块的其余部分未模拟,并在所有测试的顶层执行此操作。

我正在使用全新的create-react-app项目,并使用单个测试来观察问题。

重现步骤:

  • create-react-app test
  • 将提供的src / App.test.js用作唯一的测试文件
  • npm run test

App.test.js

jest.mock('react', () => {
  jest.dontMock('react');

  const React = require('react');
  const lazy = jest.fn();

  return {
    ...React,
    lazy
  };
});

import * as React from 'react';
const React2 = require('react');

it('should partially mock React module', async () => {
  expect(jest.isMockFunction(React.lazy)).toBe(true); // passes
  expect(jest.isMockFunction(React2.lazy)).toBe(true); // fails
  expect(jest.isMockFunction(require('react').lazy)).toBe(true); // fails
  expect(jest.isMockFunction((await import('react')).lazy)).toBe(true); // fails
});
这里的问题似乎是 jest.dontMock 阻止了对 require 和动态 import 的模拟,但目前还不清楚为什么可以用这种方式模拟静态 import ,因为它无论如何都使用了 require。以下是转换后的文件:
"use strict";

jest.mock('react', () => {
  jest.dontMock('react');

  const React = require('react');

  const lazy = jest.fn();
  return (0, _objectSpread2.default)({}, React, {
    lazy
  });
});

var _interopRequireWildcard3 = require("...\\node_modules\\@babel\\runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("...\\node_modules\\@babel\\runtime/helpers/interopRequireDefault");

var _interopRequireWildcard2 = _interopRequireDefault(require("...\\node_modules\\@babel\\runtime/helpers/interopRequireWildcard"));

var _objectSpread2 = _interopRequireDefault(require("...\\node_modules\\@babel\\runtime/helpers/objectSpread"));

var React = _interopRequireWildcard3(require("react"));

const React2 = require('react');
...

这可能与create-react-app Jest+Babel设置有关,因为我无法使jest.dontMock在纯Jest和require下正常工作。

为什么静态的React导入被模拟,而React2和其余部分没有被模拟?里面到底发生了什么?

如何修复jest.dontMock当前的行为,以便在顶层部分模拟一个模块?


我认为应该像在这个问题中提到的那样使用require.requireActual。此外,使用它时不需要jest.dontMock('react')。我不知道为什么dontMock在导入和引用方面的行为不同 - 它应该在两种情况下都防止模拟。 - AWolf
1
@AWolf 我知道requireActual,但完全忘记了它,并且在你链接的问题中不知怎么忽略了它。确实,这就是解决方案,谢谢。如果您愿意,请考虑将此修复作为答案提供。 - Estus Flask
1个回答

4

默认导入:

一个简单的解决方案是在setupTest.js中模拟React.lazy:

import React from 'react';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

jest.spyOn(React.lazy);

任何后续的 require/imports 都将为每个测试文件进行部分模拟,包括 react。这是一个工作示例: https://github.com/mattcarlotta/react-lazy-mocked(我不使用create-react-app,但可以像我一样设置jest) 安装步骤如下:git clone git@github.com:mattcarlotta/react-lazy-mocked.gitcd react-lazy-mockedyarn installyarn test。在root/__tests__/root.test.js中有更多信息。请注意保留HTML标记。
import React from 'react';
import App from '../index.js';

const React2 = require('react');

describe('App', () => {
  const wrapper = mount(<App />);

  it('renders without errors', () => {
    const homeComponent = wrapper.find('.app');
    expect(homeComponent).toHaveLength(1);
  });

  it('should partially mock React module', async () => {
    expect(jest.isMockFunction(await require('react').lazy)).toBe(true); // eslint-disable-line global-require
    expect(jest.isMockFunction(React)).toBe(false);
    expect(jest.isMockFunction(React.lazy)).toBe(true);
    expect(jest.isMockFunction(React2)).toBe(false);
    expect(jest.isMockFunction(React2.lazy)).toBe(true);
  });

  it('should no longer be partially mocked within the test file', () => {
    React.lazy.mockRestore();
    expect(jest.isMockFunction(React.lazy)).toBe(false);
  });
});

pages/Home/__tests__/Home.test.js

import React from 'react';
import Home from '../index.js';

describe('Home', () => {
  const wrapper = shallow(<Home />);

  it('renders without errors', () => {
    const homeComponent = wrapper.find('.app');
    expect(homeComponent).toHaveLength(1);
  });

  it('should partially mock React module', async () => {
    expect(jest.isMockFunction(React.lazy)).toBe(true);
  });
});

命名导入:

工作示例: https://github.com/mattcarlotta/named-react-lazy-mocked

安装:

  • git clone git@github.com:mattcarlotta/named-react-lazy-mocked.git
  • cd named-react-lazy-mocked
  • yarn install
  • yarn test

utils/__mocks__/react.js

jest.mock('react', () => ({
  ...require.requireActual('react'),
  lazy: jest.fn(),
}));
module.exports = require.requireMock('react');

utils/setup/setupTest.js(可以选择将模拟的react文件作为global jest函数添加,这样您就不必为每个测试编写import * as React from 'react'):

import { JSDOM } from 'jsdom';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
// import React from '../__mocks__/react';

configure({ adapter: new Adapter() });

// global.React = React;

root/__tests__/root.test.js

import * as React from 'react';
import App from '../index.js';

const React2 = require('react');

describe('App', () => {
  const wrapper = mount(<App />);

  it('renders without errors', () => {
    const homeComponent = wrapper.find('.app');
    expect(homeComponent).toHaveLength(1);
  });

  it('should partially mock React module', async () => {
    expect(jest.isMockFunction(await require('react').lazy)).toBe(true); // eslint-disable-line global-require
    expect(jest.isMockFunction(React)).toBe(false);
    expect(jest.isMockFunction(React.lazy)).toBe(true);
    expect(jest.isMockFunction(React2)).toBe(false);
    expect(jest.isMockFunction(React2.lazy)).toBe(true);
  });
});

1
感谢您提供详细的答案。不幸的是,这对我的情况不起作用,因为问题是模拟命名导入。如果使用默认的import React from 'react',事情可能会更简单,但我没有使用它。React 导入通常被用作 import React, { lazy } from 'react',其中 React 默认导出仅由 Babel/TS 使用,以为 JSX 转换提供 React.createElement - Estus Flask
你的意思是像这样 import * as React from 'react'; React.lazy = jest.fn() 吗?由于 Babel 的 ES 模块互操作方式对于 * 的处理方式,这种方法行不通。 - Estus Flask
已经解决了命名导出的问题。更新了答案以包括两者。 - Matt Carlotta
谢谢,这就是我现在的做法。 - Estus Flask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接