我的项目中有一个非常复杂的选择器结构(某些选择器可能具有多达5个嵌套级别),因此其中一些很难通过传递输入状态进行测试,而我想模拟输入选择器。但是我发现这是不可能的。
这是最简单的示例:
// selectors1.js
export const baseSelector = createSelector(...);
-
// selectors2.js
export const targetSelector = createSelector([selectors1.baseSelector], () => {...});
我想在测试套件中拥有什么:
beforeEach(() => {
jest.spyOn(selectors1, 'baseSelector').mockReturnValue('some value');
});
test('My test', () => {
expect(selectors2.targetSelector()).toEqual('some value');
});
但是,由于targetSelector
在初始化selectors1.baseSelector
期间引用selectors2.js
并在selectors1.baseSelector
之后将模拟分配给selectors1.js
,因此这种方法不起作用。
我现在看到2种有效的解决方案:
jest.mock
模拟整个selectors1.baseSelector
模块,但是,如果我需要在某些特定情况下更改export const targetSelector = createSelector([(state) => selectors1.baseSelector(state)], () => {...});
输出,它将无法正常工作list = [50, 2, 99, 1, 958, 9, 6, 80]
p list.map(&:to_s).map { |n| n[0] }
# => ["5", "2", "9", "1", "9", "9", "6", "8"]
但是出于明显的原因,我不太喜欢这种方法。
接下来的问题是:是否有机会模拟重新选择选择器以进行单元测试?
答案 0 :(得分:2)
问题在于重新选择基于构图概念。因此,您可以从许多其他选择器中创建一个选择器。您真正需要测试的不是整个选择器,而是完成工作的最后一个函数。如果没有,则测试将彼此重复,就好像您对选择器1进行了测试,并且在选择器2中使用了选择器1一样,然后自动在选择器2测试中对它们都进行了测试。
为了实现:
仅测试选择器的结果功能。 selector.resultFunc
可以访问它。
例如:
const selector2 = createSelector(selector1, (data) => ...);
// tests
const actual = selector2.resultFunc([returnOfSelector1Mock]);
const expected = [what we expect];
expect(actual).toEqual(expected)
我们将测试定义选择器的功能,而不是测试整个组合,复制相同的断言或模拟特定选择器的输出,这样,可以通过resultFunc
键访问createSelector中的最后一个参数。
答案 1 :(得分:2)
对于任何尝试使用Typescript解决此问题的人,这篇文章最终对我有用: https://dev.to/terabaud/testing-with-jest-and-typescript-the-tricky-parts-1gnc
我的问题是我正在测试一个模块,该模块在创建请求的过程中调用了几个不同的选择器,并且执行测试时,选择器看不到我创建的redux-mock-store
状态。我最终完全跳过了模拟存储,而是模拟了被调用的特定选择器的返回数据。
过程是这样的:
import { inputsSelector, outputsSelector } from "../store/selectors";
import { mockInputsData, mockOutputsData } from "../utils/test-data";
jest.mock("../store/selectors", () => ({
inputsSelector: jest.fn(),
outputsSelector: jest.fn(),
}));
.mockImplementation
和mocked
的{{1}}助手,可以包装每个选择器并为每个选择器提供自定义返回数据。ts-jest\utils
beforeEach(() => {
mocked(inputsSelector).mockImplementation(() => {
return mockInputsData;
});
mocked(outputsSelector).mockImplementation(() => {
return mockOutputsData;
});
});
定义中执行以下操作:test()
答案 2 :(得分:1)
您可以通过模拟整个selectors1.js
模块来实现此目的,但是导入也是在测试内部,以便可以访问模拟的功能
假设您的selectors1.js
看起来像
import { createSelector } from 'reselect';
// selector
const getFoo = state => state.foo;
// reselect function
export const baseSelector = createSelector(
[getFoo],
foo => foo
);
和selectors2.js
看起来
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
函数调用将被提升到模块顶部以模拟selectors1.js
模块
当您import
/ require
selectors1.js
时,您将获得baseSelector
的模拟版本,可以在运行测试之前控制其行为
答案 3 :(得分:-1)
我遇到了同样的问题。我最终用reselect createSelector
来模拟jest.mock
和createSelector
,以忽略除最后一个参数(这是您要测试的核心函数)以外的所有参数。总体而言,这种方法对我很有帮助。
我的选择器模块中有一个循环依赖项。我们的代码库太大,我们没有足够的带宽来相应地重构它们。
我们的代码库在选择器中有很多循环依赖项。尝试重写和重构它们以使其不具有循环依赖关系是太多的工作。因此,我选择了模拟reselect
,以便不必花时间进行重构。
如果选择器的代码库干净且没有依赖关系,请务必考虑使用resultFunc
createSelector
。此处提供更多文档:https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-resultfunc
我用来模拟// 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);
});
});
正在运行的整个测试套件代码
oauth2_access_token