Модульный тест Jest для функции устранения дребезга

Я пытаюсь написать модульный тест для функции 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);
  };
}

Как мне начать?


person RecipeCreator    schedule 07.09.2018    source источник
comment
Я думал о том, чтобы смоделировать функцию CallBack для тестирования, но я хотел бы смоделировать и проверить, что функция вызывается на основе параметра ожидания, переданного дебаунсеру. Я на пути к записи?   -  person RecipeCreator    schedule 07.09.2018
comment
Что такое функция устранения дребезга в этом контексте? Для чего это используется?   -  person Peter Mortensen    schedule 09.09.2020


Ответы (4)


Вы, вероятно, захотите проверить логику в вашей функции дебаунсера:

Сказав это, похоже, что ваш настоящий вопрос касается тестирования отклоненных функций.

Тестирование отклоненных функций

Вы можете проверить, что функция не дребезжит, используя макет для отслеживания вызовов функций и поддельные таймеры для имитации течения времени.

Вот простой пример использования Jest фиктивной функции и 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
});
person Brian Adams    schedule 07.09.2018
comment
@RecipeCreator добро пожаловать в SO! Поскольку вы новичок, дружеское напоминание отметить как выполненное и проголосовать (когда вы получите эту возможность), если ответ содержит необходимую вам информацию. - person Brian Adams; 07.09.2018
comment
есть ли способ сделать это без синона? с помощью Jest Timers Mocks (jestjs.io/docs/en/timer-mocks)? - person latata; 17.05.2019
comment
@BrianAdams отличное решение! Очень легко понять. - person coloradocolby; 09.04.2020

На самом деле, вам не нужно использовать 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);
    });
});

Дополнительная информация: Моки таймера

person tswistak    schedule 07.12.2018
comment
это сработало отлично, но если вы не используете jest v27 и сталкиваетесь с ошибкой бесконечной рекурсии, см. - person mad.meesh; 16.12.2020
comment
jest.useFakeTimers(современный) const foo = jest.fn() test(timer, () =› { setTimeout(() =› foo(), 2000) jest.runAllTimers() expect(foo).toBeCalledTimes(1) } ) Вы также можете просто сделать более простой тест, как этот, и не забудьте параметр jest.useFakeTimers(), он необязателен, но может иметь значение. - person Marcus Ekström; 03.03.2021

Если в вашем коде вы делаете это:

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

Источник.

person Mohamed Elsayed    schedule 08.01.2019
comment
дразнить lodash debounce кажется ходом - person neaumusic; 30.01.2021
comment
Что сработало для меня, так это: jest.mock('lodash/debounce', () => jest.fn(fn => fn)); - person ElectroBuddha; 02.07.2021

Мне нравится, что эта аналогичная версия легче проваливается:

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);
});
person Nicc    schedule 01.10.2020
comment
это отлично сработало, но если вы не используете jest v27 и сталкиваетесь с ошибкой бесконечной рекурсии, обратитесь к: stackoverflow.com/a/64336022/ 4844024 - person mad.meesh; 16.12.2020
comment
Что вы подразумеваете под проще потерпеть неудачу? Можете ли вы уточнить? - person Peter Mortensen; 04.01.2021
comment
я имел в виду, что проще протестировать сценарий, который возвращает ложный результат. в этом случае, если мы установим для jest.advanceTimersByTime() значение 600, модульный тест завершится ошибкой, что утешит нас тем, что функция debounce работает правильно, поскольку она будет вызываться дважды. - person Nicc; 08.01.2021