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

5cg8jx4n  于 2024-01-04  发布在  Jest
关注(0)|答案(5)|浏览(177)

在我的项目中,我有一个非常复杂的选择器结构(一些选择器可能有多达5层的嵌套),所以其中一些很难通过输入状态进行测试,我想模拟输入选择器。然而,我发现这是不可能的。
下面是最简单的例子:

  1. // selectors1.js
  2. export const baseSelector = createSelector(...);

字符串

  1. // selectors2.js
  2. export const targetSelector = createSelector([selectors1.baseSelector], () => {...});


我希望在我的测试套件中有:

  1. beforeEach(() => {
  2. jest.spyOn(selectors1, 'baseSelector').mockReturnValue('some value');
  3. });
  4. test('My test', () => {
  5. expect(selectors2.targetSelector()).toEqual('some value');
  6. });


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

  1. 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函数:
  1. import { inputsSelector, outputsSelector } from "../store/selectors";
  2. import { mockInputsData, mockOutputsData } from "../utils/test-data";
  3. jest.mock("../store/selectors", () => ({
  4. inputsSelector: jest.fn(),
  5. outputsSelector: jest.fn(),
  6. }));

字符串

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

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

展开查看全部
oknwwptz

oknwwptz2#

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

  1. import { baseSelector } from "../store/selectors";
  2. jest.mock("../store/selectors", () => ({
  3. baseSelector: jest.fn(),
  4. }));

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

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

piztneat

piztneat3#

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

我遇到的问题

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

为什么我使用这种方法?

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

我用来嘲笑createSelector的代码

  1. // Mock the reselect
  2. // This mocking call will be hoisted to the top (before import)
  3. jest.mock('reselect', () => ({
  4. createSelector: (...params) => params[params.length - 1]
  5. }));

字符串

然后访问创建的选择器,我有这样的东西

  1. const myFunc = TargetSelector.IsCurrentPhaseDraft;

整个测试套件代码运行

  1. // Mock the reselect
  2. // This mocking call will be hoisted to the top (before import)
  3. jest.mock('reselect', () => ({
  4. createSelector: (...params) => params[params.length - 1]
  5. }));
  6. import * as TargetSelector from './TicketFormStateSelectors';
  7. import { FILTER_VALUES } from '../../AppConstant';
  8. describe('TicketFormStateSelectors.IsCurrentPhaseDraft', () => {
  9. const myFunc = TargetSelector.IsCurrentPhaseDraft;
  10. it('Yes Scenario', () => {
  11. expect(myFunc(FILTER_VALUES.PHASE_DRAFT)).toEqual(true);
  12. });
  13. it('No Scenario', () => {
  14. expect(myFunc(FILTER_VALUES.PHASE_CLOSED)).toEqual(false);
  15. expect(myFunc('')).toEqual(false);
  16. });
  17. });

展开查看全部
ao218c7q

ao218c7q4#

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

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

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

  1. const selector2 = createSelector(selector1, (data) => ...);
  2. // tests
  3. const actual = selector2.resultFunc([returnOfSelector1Mock]);
  4. const expected = [what we expect];
  5. expect(actual).toEqual(expected)

字符串

摘要

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

展开查看全部
uklbhaso

uklbhaso5#

您可以通过模拟整个selectors1.js模块来实现这一点,但也可以在测试内部导入,以便访问模拟的功能
假设您的selectors1.js看起来像

  1. import { createSelector } from 'reselect';
  2. // selector
  3. const getFoo = state => state.foo;
  4. // reselect function
  5. export const baseSelector = createSelector(
  6. [getFoo],
  7. foo => foo
  8. );

字符串
selectors2.js看起来像

  1. import { createSelector } from 'reselect';
  2. import selectors1 from './selectors1';
  3. export const targetSelector = createSelector(
  4. [selectors1.baseSelector],
  5. foo => {
  6. return foo.a;
  7. }
  8. );


然后你可以写一些测试,比如

  1. import { baseSelector } from './selectors1';
  2. import { targetSelector } from './selectors2';
  3. // This mocking call will be hoisted to the top (before import)
  4. jest.mock('./selectors1', () => ({
  5. baseSelector: jest.fn()
  6. }));
  7. describe('selectors', () => {
  8. test('foo.a = 1', () => {
  9. const state = {
  10. foo: {
  11. a: 1
  12. }
  13. };
  14. baseSelector.mockReturnValue({ a: 1 });
  15. expect(targetSelector(state)).toBe(1);
  16. });
  17. test('foo.a = 2', () => {
  18. const state = {
  19. foo: {
  20. a: 1
  21. }
  22. };
  23. baseSelector.mockReturnValue({ a: 2 });
  24. expect(targetSelector(state)).toBe(2);
  25. });
  26. });


jest.mock函数调用将被提升到模块的顶部以模拟selectors1.js模块当您执行import/requireselectors1.js时,您将获得baseSelector的模拟版本,您可以在运行测试之前控制其行为

展开查看全部

相关问题