单元测试中的可变范围问题
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;
我对 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;