调度自定义事件并测试它是不是被正确触发(React TypeScript,Jest)

Posted

技术标签:

【中文标题】调度自定义事件并测试它是不是被正确触发(React TypeScript,Jest)【英文标题】:Dispatch a Custom Event and test if it was correctly triggered (React TypeScript, Jest)调度自定义事件并测试它是否被正确触发(React TypeScript,Jest) 【发布时间】:2022-01-20 19:32:42 【问题描述】:

我正在尝试验证位于反应函数 useEffect 挂钩内的自定义事件侦听器,如下所示:


export interface specialEvent extends Event 
    detail?: string


function Example() 
    React.useEffect(()=>
         document.addEventListener('specialEvent', handleChange)
         return () => 
               document.removeEventListener('specialEvent',handleChange)
          
    )
    const handleChange = (event:SpecialEvent) => 
       ...
      


我想触发这个自定义事件监听器并开玩笑地测试它:

it('should trigger "specialEvent" event Listener Properly', async () => 
        const specialEvent = new CustomEvent('specialEvent')
        const handleChange = jest.fn()
        render(<Example />)
        await waitFor(() => 
            window.document.dispatchEvent(specialEvent)
            expect(window.document.dispatchEvent).toHaveBeenNthCalledWith(1, 'specialEvent')
            expect(specialEvent).toHaveBeenCalledTimes(1)
        )
    )

这段代码给了我以下错误:

expect(received).toHaveBeenNthCalledWith(n, ...expected)

    Matcher error: received value must be a mock or spy function

    Received has type:  function
    Received has value: [Function dispatchEvent]

按照其中一个答案的建议,我尝试了这个:

//Assert Statements

const specialEvent = new CustomEvent('specialEvent');
const handleSelect = jest.fn();
act(() =>  
  render(<Example />) 
);
await waitFor(() =>  
  window.document.dispatchEvent(specialEvent) 
  expect(handleSelect).toHaveBeenCalledTimes(1) 
);

但这一次它说预期的呼叫是 1 但收到的是 0。

谁能帮我解决这个问题?

【问题讨论】:

【参考方案1】:

在测试时,导致 React 状态更新的代码应该包装到 act(...) 中。如果 handleChange 不会导致 React 状态更新,则不需要使用 act

此外,最好不要测试实现细节,对于您的情况,测试实现细节语句是:

expect(window.document.dispatchEvent).toHaveBeenNthCalledWith(1, 'specialEvent')
expect(specialEvent).toHaveBeenCalledTimes(1)

对实现细节的每一个小改动都会导致需要修改测试用例。我们应该从用户的角度来测试 UI,用户并不关心 UI 的实现细节,只关心 UI 的正确渲染。

您应该测试的是:当触发自定义事件并且在事件处理程序中更改状态时,组件的输出会发生什么。

例如

index.tsx:

import React,  useState  from 'react';

export interface SpecialEvent extends Event 
  detail?: string;


export function Example() 
  const [changed, setChanged] = useState(false);
  React.useEffect(() => 
    document.addEventListener('specialEvent', handleChange);
    return () => 
      document.removeEventListener('specialEvent', handleChange);
    ;
  );
  const handleChange = (event: SpecialEvent) => 
    console.log(event);
    setChanged((pre) => !pre);
  ;
  return <div>changed ? 'a' : 'b'</div>;

index.test.tsx:

import  render, screen, act  from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';
import  Example  from './';

describe('70400540', () => 
  test('should pass', () => 
    const specialEvent = new CustomEvent('specialEvent');
    render(<Example />);
    expect(screen.getByText('b')).toBeInTheDocument();
    act(() => 
      window.document.dispatchEvent(specialEvent);
    );
    expect(screen.getByText('a')).toBeInTheDocument();
  );
);

window.document.dispatchEvent(specialEvent) 会导致 React 状态发生变化,所以我们将其包装成 act(...)

测试结果:

 PASS  examples/70400540/index.test.tsx (11.259 s)
  70400540
    ✓ should pass (59 ms)

  console.log
    CustomEvent  isTrusted: [Getter] 

      at Document.handleChange (examples/70400540/index.tsx:16:13)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        12.655 s

软件包版本:

"@testing-library/react": "^11.2.2",
"react": "^16.14.0",
"jest": "^26.6.3",

【讨论】:

【参考方案2】:

正如错误消息所述,toHaveBeenNthCalledWith 匹配器需要将mock 或间谍传递给expect

但是,您可能不需要对 window.document.dispatchEvent 被调用做出任何断言,因为您知道您在测试中的上述行中调用了它。

有关更多信息,请在此处查看toHaveBeenNthCalledWith 上的文档:https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-

【讨论】:

我将代码更新为:expect(specialEvent).toHaveBeenCalledTimes(1),但它显示预期:1 调用:0。你知道可能出了什么问题:``` const specialEvent = new CustomEvent('specialEvent') const handleSelect = jest.fn() act(() => render() ) await waitFor(() => window.document.dispatchEvent(specialEvent) expect(handleSelect).toHaveBeenCalledTimes(1) ) ```

以上是关于调度自定义事件并测试它是不是被正确触发(React TypeScript,Jest)的主要内容,如果未能解决你的问题,请参考以下文章

不会触发自定义事件

SwiftUI 接收自定义事件

react native 中的 branch.io 自定义事件未触发奖励规则

Mysql存储过程触发器事件调度器使用入门

使用 Dojo 框架调度自定义事件

为啥 jest 不能为我正在测试的自定义 React 钩子提供 useTranslation 钩子?