发送自定义事件并测试它是否被正确触发(React TypeScript、Jest)

Dispatch a Custom Event and test if it was correctly triggered (React TypeScript, Jest)

我正在尝试验证位于反应函数 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。

谁能帮我解决这个问题?

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

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

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

测试时,引起 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",