Jest.js 有没有一种方法可以正确地模拟单元测试中的Reselect选择器?

5cg8jx4n  于 11个月前  发布在  Jest
关注(0)|答案(5)|浏览(124)

在我的项目中,我有一个非常复杂的选择器结构(一些选择器可能有多达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');
});


但是,这种方法不起作用,因为targetSelectorselectors2.js初始化期间引用selectors1.baseSelector,然后mock被分配给selectors1.baseSelector
我现在看到的两个解决方案是:
1.用jest.mock模拟整个selectors1.js模块,但是,如果我需要在某些特定情况下更改selectors1.baseSelector输出,那么它将不起作用
1.像这样 Package 每个依赖选择器:

export const targetSelector = createSelector([(state) => selectors1.baseSelector(state)], () => {...});


但我不太喜欢这种方法,原因很明显。
所以,接下来的问题是:有没有机会在单元测试中正确地模拟Reselect选择器?

hrysbysz

hrysbysz1#

对于任何试图使用Typescript解决这个问题的人来说,这篇文章最终对我有效:https://dev.to/terabaud/testing-with-jest-and-typescript-the-tricky-parts-1gnc
我的问题是,我正在测试一个模块,在创建请求的过程中调用了几个不同的选择器,而我创建的redux-mock-store状态在测试执行时对选择器不可见。我最终跳过了mock store,而是模仿了被调用的特定选择器的返回数据。
过程是这样的:

  • 导入选择器并将其注册为jest函数:
import { inputsSelector, outputsSelector } from "../store/selectors";         
    import { mockInputsData, mockOutputsData } from "../utils/test-data";

    jest.mock("../store/selectors", () => ({
      inputsSelector: jest.fn(),
      outputsSelector: jest.fn(),
    }));

字符串

  • 然后使用.mockImplementation沿着ts-jest\utils中的mocked帮助器,它允许您 Package 每个选择器并为每个选择器给予自定义返回数据。
beforeEach(() => {
        mocked(inputsSelector).mockImplementation(() => {
            return mockInputsData;
        });
        mocked(outputsSelector).mockImplementation(() => {
            return mockOutputsData;
        });
    });

  • 如果你需要在特定的测试中覆盖选择器的默认返回值,你可以在test()定义中这样做:
test("returns empty list when output data is missing", () => {
        mocked(outputsSelector).mockClear();
        mocked(outputsSelector).mockImplementationOnce(() => {
            return [];
        });
        // ... rest of your test code follows ...
    });

oknwwptz

oknwwptz2#

除了@nate-peters的回答,我想分享一个更简单的版本:

import { baseSelector } from "../store/selectors";         

jest.mock("../store/selectors", () => ({
  baseSelector: jest.fn(),
}));

字符串
然后在每个it(...)块中,您可以实现

it('returns ...', () => {
    baseSelector.mockReturnValueOnce(values);
    //...rest of you code

piztneat

piztneat3#

我也遇到了同样的问题。我最终用jest.mock模拟了reselect createSelector,在测试时忽略了除了最后一个参数(这是你想要测试的核心函数)之外的所有参数。总的来说,这种方法对我很有用。

我遇到的问题

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

为什么我使用这种方法?

我们的代码库在选择器中有很多循环依赖。试图重写和重构它们以消除循环依赖是太多的工作。因此,我选择了mock createSelector,这样我就不必花时间重构。
如果您的选择器代码库是干净的,没有依赖性,那么一定要考虑使用reselectresultFunc。更多文档请访问:https://github.com/reduxjs/reselect#createselectorinputselectors--inputselectors-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);
  });
});

ao218c7q

ao218c7q4#

Reselect是基于组合概念的。所以你从许多其他选择器中创建了一个选择器。你真正需要测试的不是整个选择器,而是完成这项工作的最后一个函数。如果不是,测试将彼此重复,就好像你有selector1的测试,selector1在selector2中使用,然后你自动在selector2测试中测试它们。
为了实现:

  • 少嘲笑
  • 不需要专门模拟组合选择器的结果
  • 无重复试验

只测试选择器的结果函数。它可以通过selector.resultFunc访问。
例如:

const selector2 = createSelector(selector1, (data) => ...);

// tests

const actual = selector2.resultFunc([returnOfSelector1Mock]);
const expected = [what we expect];
expect(actual).toEqual(expected)

字符串

摘要

我们没有测试整个组合,也没有重复相同的Assert,或者模拟特定的选择器输出,而是测试定义选择器的函数,因此,选择器中的最后一个参数,可以通过resultFunc键访问。

uklbhaso

uklbhaso5#

您可以通过模拟整个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/requireselectors1.js时,您将获得baseSelector的模拟版本,您可以在运行测试之前控制其行为

相关问题