Javascript 有没有一种方法可以正确模拟重新选择选择器进行单元测试?
我的项目中有一个非常复杂的选择器结构(一些选择器可能有多达5个嵌套级别),因此其中一些选择器很难通过输入状态进行测试,我想模拟输入选择器。然而,我发现这是不可能的 以下是最简单的示例:Javascript 有没有一种方法可以正确模拟重新选择选择器进行单元测试?,javascript,unit-testing,jestjs,reselect,Javascript,Unit Testing,Jestjs,Reselect,我的项目中有一个非常复杂的选择器结构(一些选择器可能有多达5个嵌套级别),因此其中一些选择器很难通过输入状态进行测试,我想模拟输入选择器。然而,我发现这是不可能的 以下是最简单的示例: // selectors1.js export const baseSelector = createSelector(...); - 我希望在测试套件中包含的内容: beforeEach(() => { jest.spyOn(selectors1, 'baseSelector').mockRetur
// selectors1.js
export const baseSelector = createSelector(...);
-
我希望在测试套件中包含的内容:
beforeEach(() => {
jest.spyOn(selectors1, 'baseSelector').mockReturnValue('some value');
});
test('My test', () => {
expect(selectors2.targetSelector()).toEqual('some value');
});
但是,这种方法不起作用,因为targetSelector
在selectors2.js
的初始化过程中获得了对selectors1.baseSelector
的引用,并且mock被分配给selectors1.baseSelector
我现在看到了两种可行的解决方案:
jest.Mock
模拟整个selectors1.js
模块,但是,如果我需要为某些特定情况更改selectors1.baseSelector
输出,它将无法工作但我不太喜欢这种方法,原因很明显
因此,接下来的问题是:是否有机会为单元测试正确模拟重新选择选择器?您可以通过模拟整个
selectors1.js
模块来实现这一点,但也可以导入内部测试,以便访问模拟的功能
假设您的选择器1.js
import { createSelector } from 'reselect';
// selector
const getFoo = state => state.foo;
// reselect function
export const baseSelector = createSelector(
[getFoo],
foo => foo
);
import { createSelector } from 'reselect';
import selectors1 from './selectors1';
export const targetSelector = createSelector(
[selectors1.baseSelector],
foo => {
return foo.a;
}
);
而selectors2.js
看起来像
import { createSelector } from 'reselect';
// selector
const getFoo = state => state.foo;
// reselect function
export const baseSelector = createSelector(
[getFoo],
foo => foo
);
import { createSelector } from 'reselect';
import selectors1 from './selectors1';
export const targetSelector = createSelector(
[selectors1.baseSelector],
foo => {
return foo.a;
}
);
然后你可以写一些测试,比如
import { baseSelector } from './selectors1';
import { targetSelector } from './selectors2';
// This mocking call will be hoisted to the top (before import)
jest.mock('./selectors1', () => ({
baseSelector: jest.fn()
}));
describe('selectors', () => {
test('foo.a = 1', () => {
const state = {
foo: {
a: 1
}
};
baseSelector.mockReturnValue({ a: 1 });
expect(targetSelector(state)).toBe(1);
});
test('foo.a = 2', () => {
const state = {
foo: {
a: 1
}
};
baseSelector.mockReturnValue({ a: 2 });
expect(targetSelector(state)).toBe(2);
});
});
jest.mock
函数调用将被提升到模块顶部,以模拟选择器1.js
模块
当您导入
/require
选择器1.js
时,您将获得模拟版本的baseSelector,您可以在运行测试之前控制其行为问题在于重新选择是基于组合概念的。因此,您可以从许多其他选择器中创建一个选择器。真正需要测试的不是整个选择器,而是完成此任务的最后一个函数。如果没有,测试将彼此重复,就像您有针对selector1的测试,并且selector1在selector2中使用一样,然后自动在selector2测试中测试这两个测试
为了实现:
- 少嘲弄
- 不需要特别模拟组合选择器的结果
- 无重复测试
选择器访问它。resultFunc
例如:
const selector2 = createSelector(selector1, (data) => ...);
// tests
const actual = selector2.resultFunc([returnOfSelector1Mock]);
const expected = [what we expect];
expect(actual).toEqual(expected)
总结
我们没有测试整个组合,也没有复制相同的断言,也没有模拟特定的选择器输出,而是测试定义选择器的函数,因此createSelector中的最后一个参数可以通过
resultFunc
键访问。我遇到了同样的问题。最后,我用jest.mock
模拟了reselectcreateselector
,以在测试时忽略除最后一个参数(您要测试的核心函数)之外的所有参数。总的来说,这种方法对我很有用
我的问题
我的选择器模块中有一个循环依赖项。我们的代码库太大,我们没有相应的带宽来重构它们
我为什么使用这种方法?
我们的代码库在选择器中有很多循环依赖项。试图重写和重构它们,使其不具有循环依赖关系,这是太多的工作。因此,我选择模拟createSelector
,这样我就不必花时间重构
如果选择器的代码库干净且没有依赖项,请务必使用reselect
resultFunc
。此处有更多文档:
我用来模拟createSelector的代码
// Mock the reselect
// This mocking call will be hoisted to the top (before import)
jest.mock('reselect', () => ({
createSelector: (...params) => params[params.length - 1]
}));
然后,为了访问创建的选择器,我使用了如下内容
const myFunc = TargetSelector.IsCurrentPhaseDraft;
整个测试套件代码都在运行
// Mock the reselect
// This mocking call will be hoisted to the top (before import)
jest.mock('reselect', () => ({
createSelector: (...params) => params[params.length - 1]
}));
import * as TargetSelector from './TicketFormStateSelectors';
import { FILTER_VALUES } from '../../AppConstant';
describe('TicketFormStateSelectors.IsCurrentPhaseDraft', () => {
const myFunc = TargetSelector.IsCurrentPhaseDraft;
it('Yes Scenario', () => {
expect(myFunc(FILTER_VALUES.PHASE_DRAFT)).toEqual(true);
});
it('No Scenario', () => {
expect(myFunc(FILTER_VALUES.PHASE_CLOSED)).toEqual(false);
expect(myFunc('')).toEqual(false);
});
});
对于任何试图用Typescript解决这个问题的人来说,这篇文章最终对我起了作用:
我的问题是,我正在测试一个模块,该模块在创建请求的过程中调用了几个不同的选择器,而我创建的redux mock store
状态在测试执行时对选择器不可见。我最终完全跳过了模拟存储,而是模拟了调用的特定选择器的返回数据
过程如下:
- 导入选择器并将其注册为jest函数:
- 然后使用
.mockImplementation
以及ts jest\utils
中的mocked
帮助程序,这样可以包装每个选择器并为每个选择器提供自定义返回数据
- 如果需要覆盖特定测试中选择器的默认返回值,可以在
test()
定义中这样做:
如果我理解正确,selectors2
正在创建一个新的selectors1
实例,因此它不是您用spy方法模拟的实例,但您希望它是吗?一个选项可能是使用依赖项注入,以便可以使用模拟实例对其进行初始化。另一种可能是模拟selector2的目标选择器以返回模拟的实例1,但出于可伸缩性的原因,您似乎在试图避免这种情况。我觉得我可能没有掌握问题的全部领域,或者可能没有掌握createSelector
正在做什么。您已经接触到使用模块模拟。是的,这正是我想要实现的。谢谢你的帮助!似乎您应该将选择器中输入选择器的数量(而不是集合)作为单个参数传递给resultFunc
。因此,不要使用selector2.resultFunc([returnOfSelector1Mock])代码>应该是选择器2.resultFunc(返回选择器1锁)代码>嗯,这是一个有趣的解决方案,但是我认为Maciej Sikora的答案是最好的。唯一的t
import { inputsSelector, outputsSelector } from "../store/selectors";
import { mockInputsData, mockOutputsData } from "../utils/test-data";
jest.mock("../store/selectors", () => ({
inputsSelector: jest.fn(),
outputsSelector: jest.fn(),
}));
beforeEach(() => {
mocked(inputsSelector).mockImplementation(() => {
return mockInputsData;
});
mocked(outputsSelector).mockImplementation(() => {
return mockOutputsData;
});
});
test("returns empty list when output data is missing", () => {
mocked(outputsSelector).mockClear();
mocked(outputsSelector).mockImplementationOnce(() => {
return [];
});
// ... rest of your test code follows ...
});