单元测试中的可变范围问题

Variable scope problems in unit tests

我对 React 组件的测试如下所示(基于 this article):

// MyComponent.test.js


import { mount } from 'enzyme';
import MyComponent from './MyComponent.jsx';

describe('<MyComponent />', () => {
  let props;
  let state;
  let mountedComponent;

  // The problematic part to be changed
  const component = () => {
    if (!mountedComponent) {
      // This enzyme mount is actually much more complex, 
      // as I'm wrapping all sorts of contexts and globals around it
      // which is why I want to take this function outside,
      // and use it as boilerplate in every test
      mountedComponent = mount(<MyComponent {...props} />);
    }
    return mountedComponent;
  };

  beforeEach(() => {
    props = {};
    state = {};
    mountedComponent = undefined;
  });

  it('Works', () => {
    state = { val: true };
    component().setState(state,
      () => expect(component().state('val')).to.equal(true),
    );
  });
});

这很好用,component() 正常运行 returns 如果在同一个 it 中多次调用相同的 mountedComponent,因为 mountedComponent 的当前值被保留在调用之间,并且仅在每次测试之前重置。

现在,如果我将此测试之外的 component() 函数提取到另一个文件:

// getMountedComponent.js

const getMountedComponent = (AnyComponent, props, mountedComponent) => {

  if (!mountedComponent) {
    // Appears not to properly reassign mountedComponent
    mountedComponent = mount(<AnyComponent {...props} />);
  }
  return mountedComponent;
};

并将 component() 函数替换为:

// MyComponent.test.js

// Cleaner problematic part
const component = () => getMountedComponent(MyComponent, props, mountedComponent);

然后这个测试失败了,因为 component() returns 第二次是一个新的组件,state = null。

这似乎是一个范围问题,但我无法解决这个问题?

Javascript by reference vs. by value

Javascript is always pass by value, but when a variable refers to an object (including arrays), the "value" is a reference to the object.

当您将 getMountedComponent 函数移到外部时,您不再在 describe 函数中设置 mountedComponent 变量,因此将其保留 undefined,因此每次都会创建 mountedComponent 的新副本。

const getMountedComponent = (MyComponent, props, mountedComponent) => {

  if (!mountedComponent) {
    // You are not changing the "mountedComponent" defined in your "describe" here
    // because "mountedComponent" is pass by value
    mountedComponent = mount(<MyComponent {...props} />);
  }
  return mountedComponent;
};

describe('<MyComponent />', () => {
  let props;
  let state;
  let mountedComponent; // This stays undefined
  // ...
}

问题是您的 getMountedComponent 函数接受 mountedComponent 参数 - 实际上它在该函数内创建了新的 mountedComponent 变量,因此它覆盖了 [=15] 中定义的同名变量=] 块。因此,每次调用 getMountedComponent 时,它都会创建新的局部变量,因此您永远不会将任何值分配给 describe 范围内定义的 mountedComponent 变量。要修复它,您可以在函数本身上缓存组件(函数是 JS 中的第一个 class 对象)插入使用外部变量:

function getMountedComponent(MyComponent, props) {

  if (!getMountedComponent.mountedComponent) {
    // Appears not to properly reassign mountedComponent
    getMountedComponent.mountedComponent = mount(<MyComponent {...props} />);
  }
  return getMountedComponent.mountedComponent;
};

要清除函数缓存只需使用这个:

delete getMountedComponent.mountedComponent;