玩笑单元测试的反跳功能

Rec*_*tor 6 javascript unit-testing jestjs debounce

我正在尝试为去抖功能编写单元测试。我很难考虑这一点。

这是代码:

function debouncer(func, wait, immediate) {
  let timeout;

  return (...args) => {
    clearTimeout(timeout);

    timeout = setTimeout(() => {
      timeout = null;
      if (!immediate) func.apply(this, args);
    }, wait);

    if (immediate && !timeout) func.apply(this, args);
  };
}
Run Code Online (Sandbox Code Playgroud)

我应该如何开始?

小智 24

实际上,您不需要使用Sinon 来测试去抖动。Jest 可以模拟 JavaScript 代码中的所有计时器。

查看以下代码(它是 TypeScript,但您可以轻松将其转换为 JavaScript):

import * as _ from 'lodash';

// Tell Jest to mock all timeout functions
jest.useFakeTimers();

describe('debounce', () => {

    let func: jest.Mock;
    let debouncedFunc: Function;

    beforeEach(() => {
        func = jest.fn();
        debouncedFunc = _.debounce(func, 1000);
    });

    test('execute just once', () => {
        for (let i = 0; i < 100; i++) {
            debouncedFunc();
        }

        // Fast-forward time
        jest.runAllTimers();

        expect(func).toBeCalledTimes(1);
    });
});
Run Code Online (Sandbox Code Playgroud)

更多信息:定时器模拟

  • 这很好用,但如果您不使用 jest v27 并遇到无限递归错误,请参阅:/sf/answers/4503521571/ (3认同)
  • jest.useFakeTimers("modern") const foo = jest.fn() test("timer", () =&gt; { setTimeout(() =&gt; foo(), 2000) jest.runAllTimers() Expect(foo).toBeCalledTimes (1) }) 你也可以像这样做一个更简单的测试,不要忘记`jest.useFakeTimers()`的参数,它是可选的,但可以产生很大的不同。 (2认同)

Mis*_*s94 16

如果在您的代码中您这样做:

import debounce from 'lodash/debounce';

myFunc = debounce(myFunc, 300);
Run Code Online (Sandbox Code Playgroud)

并且您想测试该函数myFunc或调用它的函数,那么在您的测试中,您可以模拟debounceusing的实现jest以使其仅返回您的函数:

import debounce from 'lodash/debounce';

// Tell Jest to mock this import
jest.mock('lodash/debounce');

it('my test', () => {
    // ...
    debounce.mockImplementation(fn => fn); // Assign the import a new implementation. In this case it's to execute the function given to you
    // ...
});
Run Code Online (Sandbox Code Playgroud)

来源:https : //gist.github.com/apieceofbart/d28690d52c46848c39d904ce8968bb27

  • 对我有用的是: `jest.mock('lodash/debounce', () =&gt; jest.fn(fn =&gt; fn));` (2认同)

Bri*_*ams 10

您可能需要检查去抖功能中的逻辑:

话虽如此,听起来您的真正问题是关于测试去抖动功能。

测试去抖动功能

您可以通过使用模拟来跟踪功能调用和使用伪造的计时器来模拟时间的流逝来测试功能是否被去抖动。

这是一个使用Jest模拟功能和使用from进行反跳功能的Sinon伪计时器的简单示例:debounce()Lodash

const _ = require('lodash');
import * as sinon from 'sinon';

let clock;

beforeEach(() => {
  clock = sinon.useFakeTimers();
});

afterEach(() => {
  clock.restore();
});

test('debounce', () => {
  const func = jest.fn();
  const debouncedFunc = _.debounce(func, 1000);

  // Call it immediately
  debouncedFunc();
  expect(func).toHaveBeenCalledTimes(0); // func not called

  // Call it several times with 500ms between each call
  for(let i = 0; i < 10; i++) {
    clock.tick(500);
    debouncedFunc();
  }
  expect(func).toHaveBeenCalledTimes(0); // func not called

  // wait 1000ms
  clock.tick(1000);
  expect(func).toHaveBeenCalledTimes(1);  // func called
});
Run Code Online (Sandbox Code Playgroud)


Arc*_*ano 5

使用现代的假定时器(Jest 27 已经默认)你可以更简洁地测试它:

import debounce from "lodash.debounce";
describe("debounce", () => {
  beforeEach(() => {
    jest.useFakeTimers("modern");
  });
  afterEach(() => {
    jest.useRealTimers();
  });
  it("should work properly", () => {
    const callback = jest.fn();
    const debounced = debounce(callback, 500);
    debounced();
    expect(callback).not.toBeCalled();

    jest.advanceTimersByTime(100);
    debounced();
    expect(callback).not.toBeCalled();

    jest.advanceTimersByTime(499);
    expect(callback).not.toBeCalled();

    jest.advanceTimersByTime(1);
    expect(callback).toBeCalledTimes(1);
  });

  it("should fire with lead", () => {
    const callback = jest.fn();
    const debounced = debounce(callback, 500, { leading: true });
    expect(callback).not.toBeCalled();
    debounced();
    expect(callback).toBeCalledTimes(1);

    jest.advanceTimersByTime(100);
    debounced();
    expect(callback).toBeCalledTimes(1);

    jest.advanceTimersByTime(499);
    expect(callback).toBeCalledTimes(1);

    jest.advanceTimersByTime(1);
    expect(callback).toBeCalledTimes(2);
  });
});
Run Code Online (Sandbox Code Playgroud)

您可以将其实现为一个像这样去抖的状态钩子......

import debounce from "lodash.debounce";
import { Dispatch, useCallback, useState } from "react";

export function useDebouncedState<S>(
  initialValue: S,
  wait: number,
  debounceSettings?: Parameters<typeof debounce>[2]
): [S, Dispatch<S>] {
  const [state, setState] = useState<S>(initialValue);
  const debouncedSetState = useCallback(
    debounce(setState, wait, debounceSettings),
    [wait, debounceSettings]
  );
  return [state, debouncedSetState];
}
Run Code Online (Sandbox Code Playgroud)

并测试为

/**
 * @jest-environment jsdom
 */
import { act, render, waitFor } from '@testing-library/react';
import React from 'react';
import { useDebouncedState } from "./useDebouncedState";

describe("useDebounceState", () => {
  beforeEach(() => {
    jest.useFakeTimers("modern");
  });
  afterEach(() => {
    jest.useRealTimers();
  });
  it("should work properly", async () => {
    const callback = jest.fn();
    let clickCount = 0;
    function MyComponent() {
      const [foo, setFoo] = useDebouncedState("bar", 500);
      callback();
      return <div data-testid="elem" onClick={() => { ++clickCount; setFoo("click " + clickCount); }}>{foo}</div>
    }
    const { getByTestId } = render(<MyComponent />)
    const elem = getByTestId("elem");

    expect(callback).toBeCalledTimes(1);
    expect(elem.textContent).toEqual("bar");

    jest.advanceTimersByTime(100);
    elem.click();
    expect(callback).toBeCalledTimes(1);
    expect(elem.textContent).toEqual("bar");

    jest.advanceTimersByTime(399);
    expect(callback).toBeCalledTimes(1);
    expect(elem.textContent).toEqual("bar");

    act(() => jest.advanceTimersByTime(1));

    await waitFor(() => {
      expect(callback).toBeCalledTimes(2);
      expect(elem.textContent).toEqual("click 1");
    });

    elem.click();
    await waitFor(() => {
      expect(callback).toBeCalledTimes(2);
      expect(elem.textContent).toEqual("click 1");
    });
    act(() => jest.advanceTimersByTime(500));
    await waitFor(() => {
      expect(callback).toBeCalledTimes(3);
      expect(elem.textContent).toEqual("click 2");
    });

  });
});
Run Code Online (Sandbox Code Playgroud)

源代码位于https://github.com/trajano/react-hooks-tests/tree/master/src/useDebouncedState