在 Jest 中测试回调函数

时间:2021-07-15 20:19:35

标签: javascript reactjs unit-testing testing jestjs

不用说,该应用程序比这复杂得多,但要点是相同的。我无法对应用程序进行重大更改,例如导入/导出 onSuccess 函数或更改为基于类的组件。

单击按钮会启动登录功能,我在其中传递了一个 onSuccess 功能。我想监视分析函数以确保它被调用,但在我的测试中我无法调用 onSuccess 函数

理想情况下,我想做这样的事情:

test("analyze should be called", () => {
  let analyzeSpy = jest.spyOn(Analytics, "analyze");
  onSuccess() //<- cannot do this
  expect(analyzeSpy).toHaveBeenCalledTimes(1);
});

这是应用程序:

import Analytics from "./Analytics";

export function Login({ onLoginSuccess }) {
  setTimeout(function () {
    console.log("TIMEOUT OVER");
    onLoginSuccess();
  }, 2000);
}

function App() {
  function handleClick() {
    console.log("Login");
    Login({
      onLoginSuccess: onSuccess,
    });
  }
  function onSuccess() {
    console.log("Login success");
    Analytics.analyze();
  }
  return (
    <>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        Login
      </button>
    </>
  );
}

export default App;

这是 Analytics.js:

export default {
  analyze: () => {
    console.log("Analysis done");
  },
};

我该如何测试?

1 个答案:

答案 0 :(得分:2)

使用 render() 模块的 react-dom 方法将您的组件渲染到 js-dom 提供的文档中。

使用 document.querySelector('button') 获取按钮 dom,调度鼠标点击事件。

使用 jest.useFakeTimers() 来使用标准计时器函数 (setTimeout) 的伪造版本。

分派点击事件后,使用jest.advanceTimersByTime(2000)执行宏任务队列(由setTimeout排队的任务)。

然后,做出断言。

例如

App.jsx

import React from 'react';
import Analytics from './Analytics';

export function Login({ onLoginSuccess }) {
  setTimeout(function () {
    console.log('TIMEOUT OVER');
    onLoginSuccess();
  }, 2000);
}

function App() {
  function handleClick() {
    console.log('Login');
    Login({
      onLoginSuccess: onSuccess,
    });
  }
  function onSuccess() {
    console.log('Login success');
    Analytics.analyze();
  }
  return (
    <>
      <button
        onClick={() => {
          handleClick();
        }}
      >
        Login
      </button>
    </>
  );
}

export default App;

App.test.jsx

import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { act } from 'react-dom/test-utils';
import App from './App';
import Analytics from './Analytics';

describe('68400320', () => {
  let container = null;
  beforeEach(() => {
    // setup a DOM element as a render target
    container = document.createElement('div');
    document.body.appendChild(container);
  });

  afterEach(() => {
    // cleanup on exiting
    unmountComponentAtNode(container);
    container.remove();
    container = null;
  });

  test('should pass', () => {
    const analyzeSpy = jest.spyOn(Analytics, 'analyze');
    jest.useFakeTimers();
    act(() => {
      render(<App />, container);
    });
    const button = document.querySelector('button');
    act(() => {
      button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
      jest.advanceTimersByTime(2000);
    });
    expect(analyzeSpy).toBeCalledTimes(1);
  });
});

测试结果:

PASS  examples/68400320/App.test.jsx (8.12 s)
  68400320
    ✓ should pass (52 ms)

  console.log
    Login

      at handleClick (examples/68400320/App.jsx:13:13)

  console.log
    TIMEOUT OVER

      at examples/68400320/App.jsx:6:13

  console.log
    Login success

      at onSuccess (examples/68400320/App.jsx:19:13)

  console.log
    Analysis done

      at Object.analyze (examples/68400320/Analytics.js:3:13)

--------------|---------|----------|---------|---------|-------------------
File          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
--------------|---------|----------|---------|---------|-------------------
All files     |     100 |      100 |     100 |     100 |                   
 Analytics.js |     100 |      100 |     100 |     100 |                   
 App.jsx      |     100 |      100 |     100 |     100 |                   
--------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.666 s, estimated 10 s
Ran all test suites related to changed files.