[英]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 函數中的邏輯:
timeout
將始終由最后一個if()
語句設置this
總是undefined
因為箭頭函數使用“封閉詞法上下文的this
值”並且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
或函數調用它,然后在您的測試,你可以嘲笑的實施debounce
用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
// ...
});
來源: 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;
});
有測試去抖動邏輯的基礎。
請注意,所有測試都是異步的,因為所測試的內容本身就是異步的。
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.