改变模块对象时,为什么花括号导入没有改变?

When mutating a module object, why are curly brace imports not changed?

我正在尝试整理一个代码库,该代码库执行一些直接的模块突变,而不是使用任何模拟行为,我注意到了一些奇怪的事情。

如果我从一个新的 create-react-app 开始,在测试中做这样的事情:

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
import lodash from "lodash"; 
test('renders learn react link', () => {

  // Directly mutating the module
  React.useContext = jest.fn();
  React.foo = "foo";
  lodash.drop = jest.fn(); 

  const { getByText } = render(<App />);
  const linkElement = getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

然后当我以后去使用这些突变时:

import React, {useContext, foo} from 'react';
import logo from './logo.svg';
import './App.css';
import lodash, {drop} from "lodash"; 
function App() {


  console.log(useContext, React.useContext, foo, React.foo );
  console.log(drop, lodash.drop); 
  // The rest doesn't matter. 

然后我们可以看到React.useContextReact.foolodash.drop会打印jest mock函数,而useContextfoo drop 将打印原始对象。

全文:

  console.log src/App.js:8
    [Function: useContext] [Function: mockConstructor] {
      _isMockFunction: true,
      getMockImplementation: [Function (anonymous)],
      mock: [Getter/Setter],
      mockClear: [Function (anonymous)],
      mockReset: [Function (anonymous)],
      mockRestore: [Function (anonymous)],
      mockReturnValueOnce: [Function (anonymous)],
      mockResolvedValueOnce: [Function (anonymous)],
      mockRejectedValueOnce: [Function (anonymous)],
      mockReturnValue: [Function (anonymous)],
      mockResolvedValue: [Function (anonymous)],
      mockRejectedValue: [Function (anonymous)],
      mockImplementationOnce: [Function (anonymous)],
      mockImplementation: [Function (anonymous)],
      mockReturnThis: [Function (anonymous)],
      mockName: [Function (anonymous)],
      getMockName: [Function (anonymous)]
    } undefined foo

  console.log src/App.js:9
    [Function: drop] [Function: mockConstructor] {
      _isMockFunction: true,
      getMockImplementation: [Function (anonymous)],
      mock: [Getter/Setter],
      mockClear: [Function (anonymous)],
      mockReset: [Function (anonymous)],
      mockRestore: [Function (anonymous)],
      mockReturnValueOnce: [Function (anonymous)],
      mockResolvedValueOnce: [Function (anonymous)],
      mockRejectedValueOnce: [Function (anonymous)],
      mockReturnValue: [Function (anonymous)],
      mockResolvedValue: [Function (anonymous)],
      mockRejectedValue: [Function (anonymous)],
      mockImplementationOnce: [Function (anonymous)],
      mockImplementation: [Function (anonymous)],
      mockReturnThis: [Function (anonymous)],
      mockName: [Function (anonymous)],
      getMockName: [Function (anonymous)]
    }

 PASS  src/App.test.js (6.875s)
  ✓ renders learn react link (304ms)


这是为什么?

我认为这与 import/require 解析导入的方式有关,并且这样做:

import React, {useContext} from "react"

和做

一样
import React from "react"; 
const {useContext} = React

与直觉相反。

这与创建 variables/constants 的时间有关。

在您改变对象之前“执行”导入,然后在您改变之前创建常量 foo React.foo。

const obj = {a: 1, b: 2};
const a = obj.a; // import
obj.a = 42; // your mock injection

console.log(a); // it will keep returning 1    

处理的方式是,当创建 a(在您的示例中为 foouseContext)时,它指向内存保持的位置 obj.a,其中在创建时刻包含一个1。那么obj.a指向内存中新存放的一个42位置,但是obj.aa是相互独立的

对于你的情况,我的建议是使用依赖注入解决方案。

要么 App 收到它需要的依赖的参数,然后你在测试中模拟它。 <App foo={myFooMock} /> 或另一种常用的解决方案是发送一个上下文,并有一个用于模拟测试的上下文创建者和一个用于生产应用程序的上下文创建者。

const myMockedContext = {foo: 'foo', useContext: jest.fn()};
const { getByText } = render(<App context={myMockedContext} />);

通常情况下,您希望在测试中包含尽可能多的真实代码、所有业务逻辑,以便一旦接口发生变化或类型发生错误时测试就会失败 returns 或您的不同代码区域中的任何其他意外行为。

但这是 TDD 社区中 Mockist 和 Classist 之间正在进行的讨论。

HTH