[英]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.