簡體   English   中英

開玩笑的去抖動單元測試 function

[英]Jest unit test for a debounce function

我正在嘗試為去抖動function 編寫單元測試。我很難考慮它。

這是代碼:

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);
  };
}

我應該如何開始?

實際上,您不需要使用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);
    });
});

更多信息:定時器模擬

您可能需要檢查 debouncer 函數中的邏輯:

話雖如此,聽起來您真正的問題是關於測試去抖動函數。

測試去抖動函數

您可以通過使用模擬來跟蹤函數調用和使用假計時器來模擬時間的流逝來測試函數是否已去抖動。

這是一個使用Jest Mock 函數和使用Lodash debounce()debounce()的函數的Sinon假定時器的簡單示例:

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
});

如果在您的代碼中您這樣做:

import debounce from 'lodash/debounce';

myFunc = debounce(myFunc, 300);

你要測試的功能myFunc或函數調用它,然后在您的測試,你可以嘲笑的實施debouncejest ,使其只返回你的函數:

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
    // ...
});

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

我喜歡這個更容易失敗的類似版本:

jest.useFakeTimers();
test('execute just once', () => {
    const func = jest.fn();
    const debouncedFunc = debounce(func, 500);

    // Execute for the first time
    debouncedFunc();

    // Move on the timer
    jest.advanceTimersByTime(250);
    // try to execute a 2nd time
    debouncedFunc();

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

    expect(func).toBeCalledTimes(1);
});

不知道這會如何與jest一起使用,因為我正在使用mocha,但對於那里尋找簡單解決方案的人:

it('debounce.spec', done => {
    // Arrange
    const log = sinon.spy();
    const search = debounce(log, 100);

    // Act
    search();

    // Assert
    setTimeout(() => {
      expect(log.called).to.be.true;
      done();
    }, 100);
  });

另一種方法是刷新 debounce 函數,讓它立即執行:

test('execute just once', () => {
    const func = jest.fn();
    const debouncedFunc = debounce(func, 500);

    // Execute for the first time
    debouncedFunc();
    debouncedFunc.flush();

  
    // try to execute a 2nd time
    debouncedFunc();
    debouncedFunc.flush();

    expect(func).toBeCalledTimes(1);
});

使用現代偽計時器(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);
  });
});

花了很多時間來弄清楚......終於這奏效了..

jest.mock('lodash', () => {
    const module = jest.requireActual('lodash');
    module.debounce = jest.fn(fn => fn);
    return module;
});

這是我的 3 個基本測試:

有測試去抖動邏輯的基礎。
請注意,所有測試都是異步的,因為所測試的內容本身就是異步的。

import debounce from 'lodash.debounce'

const delay = ms => new Promise(resolve => setTimeout(resolve, ms))

test('called repeatedly', async () => {
  const DELAY = 100;
  let callCount = 0;
  const debounced = debounce(() => ++callCount, DELAY)

  for( let i = 4; i--; )
    debounced()
 
  await delay(DELAY)
  expect( callCount ).toBe(1) 
})


test('called repeatedly exactly after the delay', async () => {
  const DELAY = 100;
  let callCount = 0, times = 3;
  const debounced = debounce(() => ++callCount, DELAY)

  for( let i = times; i--; ) {
    debounced()
    await delay(DELAY) 
  }
 
  await delay(DELAY * times)
  expect( callCount ).toBe(3) 
})


test('called repeatedly at an interval small than the delay', async () => {
  const DELAY = 100;
  let callCount = 0, times = 6;
  const debounced = debounce(() => ++callCount, DELAY)

  for( let i = times; i--; ) {
    debounced()
    await delay(DELAY/2) 
  }
 
  await delay(DELAY * times)
  expect( callCount ).toBe(1) 
})

這些測試是我寫的,不是從lodash debounce測試源代碼中獲取的

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM