用于链式函数调用的 Jest 单元测试

Posted

技术标签:

【中文标题】用于链式函数调用的 Jest 单元测试【英文标题】:Jest Unit Test for chained function calls 【发布时间】:2020-10-01 00:33:57 【问题描述】:

我已经为此苦苦挣扎了一段时间。单元测试新手.. 我正在尝试模拟单元测试用例 -

fetchFooter() 
    client.items().type('footer_link')
            .depthParameter(10)
            .toObservable()
            .subscribe((response) => 
                this.setState(
                    resFooter: response.items,
                    resFooterLinked: response.linkedItems,
                );
            );

客户在哪里

import  DeliveryClient  from '@kentico/kontent-delivery';

const client = new DeliveryClient(
  projectId: <projectKey>,
  enableAdvancedLogging: false,
);

这是我到目前为止写的,但似乎不起作用。

const sample = 
  items: ,
  linkedItems: 
;//this is my response object

describe("testing client", () => 
  const mockClient = 
    items: jest.fn().mockReturnThis(),
    type: jest.fn().mockReturnThis(),
    depthParameter: jest.fn().mockReturnThis(),
    toObservable: jest.fn().mockReturnThis(),
    subscribe: jest.fn().mockReturnThis(),
  ;

  const mockProps = 
    client: mockClient,
  ;
  const component = mount(
    <Footer mockProps=mockProps resFooter=true resFooterLinked=true />
  );

  describe("Component", () => 
    describe("#componentDidMount", () => 
      it("should mount the component and set state correctly", () => 
        const mockedResponse = sample;
        mockClient.subscribe.mockImplementationOnce((handler) => handler(sample));
        // tslint:disable-next-line: no-string-literal
        component["setState"] = jest.fn();
        component.instance().fetchFooter();
        expect(
          mockClient.items().type().depthParameter().toObservable().subscribe
        ).toBeCalledWith("footer_link");
        // tslint:disable-next-line: no-string-literal
        expect(component["setState"]).toBeCalledWith(sample);
      );
    );
  );
);

我收到一个错误 -

    expect(jest.fn()).toBeCalledWith(...expected)

    Expected: "footer_link"

    Number of calls: 0

也许我在这里缺少一些基础知识。我为此寻找解决方案,但不幸的是没有找到适合我的解决方案。 有人能帮忙吗?提前致谢

【问题讨论】:

【参考方案1】:

正如the reference 所说,mockReturnThis 是:

jest.fn(function () 
  return this;
);

它只使函数可链接,并且不会将参数传递给链中的下一个函数。 subscribe 不应该被 footer_link 调用,而是 type 被这个参数调用。

没有必要让模拟的subscribe 调用处理程序,这并不能保证在其中调用了setState。另外,setState 不是用sample 调用的,而是用另一个对象调用的。

没有必要在测试中调用expect(mockClient.items()...)chain,这只会增加调用次数。调用断言不允许确定调用方法的顺序,这可以手动进行。

由于模拟的客户端对象应该可以在 config 模块模拟中访问,因此需要将变量提升到模块范围,这允许在组件模块中模拟 client

可以像这样对链进行全面测试:

// module scope
jest.mock('../../config', () => (
  __esModule: true,
  default: mockClient
);

const mockClient = 
  items: jest.fn(),
  type: jest.fn(),
  ...
;
...

// describe scope
let clientCalls;
beforeEach(() => 
  clientCalls = [];

  mockClient.items.mockImplementation(function ()  clientCalls.push(1); return this );
  mockClient.type.mockImplementation(function ()  clientCalls.push(2); return this );
  ...
);
...

// test scope
component.instance().fetchFooter();

expect(clientCalls).toEqual([1,2,3,4,5]);
expect(mockClient.items).toBeCalledWith();
expect(mockClient.type).toBeCalledWith("footer_link");
...
expect(mockClient.subscribe).toBeCalledWith(expect.any(Function));

let [handler] = mockClient.subscribe.mock.calls[0];
expect(component["setState"]).not.toBeCalled();
handler();
expect(component["setState"]).toBeCalledWith( resFooter:..., resFooterLinked:...);

【讨论】:

clientCalls 给出了一个空白数组。似乎 mockClient 变量没有被分配给对象。所以我尝试在顶部将 mockClient 定义为这个对象,但仍然给我错误 - Number of calls: 0.. 我错过了什么吗? 这取决于你如何模拟它,这在问题中没有显示。如果你不这样做,mockClient 将不会被神奇地使用而不是 client。请显示client 在组件中的确切显示方式。如果已导入,则应在模块级别进行模拟。 哦.. 是的,在组件(.js)中,我已经从其他文件中导入了客户端,像这样 - import client from '../../config';... 所以我需要对测试(test.js)做一些更改为此? 是的,你肯定会。还要确保在每次测试之前重置 Jest 间谍,请参阅 jestjs.io/docs/en/configuration#restoremocks-boolean 这有帮助.. 非常感谢!

以上是关于用于链式函数调用的 Jest 单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React、Jest 和 React-testing-library 为带有令牌的 api 调用编写单元测试?

在 Jest 单元测试中模拟 imgur API 调用

ReactWrapper::state() 只能在类组件单元测试 Jest 和 Enzyme 上调用

如何在 JEST 单元测试中修复“SVG 未定义”

Jest进行前端单元测试

TypeError:运行 Jest 单元测试时,mxgraph 不是函数