有没有一种方法可以正确模拟重新选择选择器以进行单元测试?

时间:2019-04-15 20:08:46

标签: javascript unit-testing jestjs reselect

我的项目中有一个非常复杂的选择器结构(某些选择器可能具有多达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种有效的解决方案:

  1. jest.mock模拟整个selectors1.baseSelector模块,但是,如果我需要在某些特定情况下更改export const targetSelector = createSelector([(state) => selectors1.baseSelector(state)], () => {...}); 输出,它将无法正常工作
  2. 像这样包装每个依赖选择器:

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"]

但是出于明显的原因,我不太喜欢这种方法。

接下来的问题是:是否有机会模拟重新选择选择器以进行单元测试?

4 个答案:

答案 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(),
    }));
  • 然后使用.mockImplementationmocked的{​​{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.mockcreateSelector,以忽略除最后一个参数(这是您要测试的核心函数)以外的所有参数。总体而言,这种方法对我很有帮助。

我遇到的问题

我的选择器模块中有一个循环依赖项。我们的代码库太大,我们没有足够的带宽来相应地重构它们。

为什么要使用这种方法?

我们的代码库在选择器中有很多循环依赖项。尝试重写和重构它们以使其不具有循环依赖关系是太多的工作。因此,我选择了模拟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