Jest: Предоставьте API для очистки очереди разрешения обещаний

Созданный на 23 нояб. 2016  ·  46Комментарии  ·  Источник: facebook/jest

Вы хотите запросить функцию или сообщить об ошибке ?

_Feature_, я думаю, но довольно важный при тестировании кода, который использует Promise s.

Каково текущее поведение?

У меня есть компонент, который использует внутреннюю упаковку и цепочку Promise после внешнего асинхронного действия. Я предоставляю имитацию асинхронного действия и выполняю обещание, которое оно возвращает в моем тесте.

Компонент примерно такой:

class Component extends React.Component {
  // ...
  load() {
    Promise.resolve(this.props.load())
      .then(
        result => result
          ? result
          : Promise.reject(/* ... */)
        () => Promise.reject(/* ... */)
      )
      .then(result => this.props.afterLoad(result));
  }
}

А тестовый код выглядит примерно так:

const load = jest.fn(() => new Promise(succeed => load.succeed = succeed));
const afterLoad = jest.fn();
const result = 'mock result';
mount(<Component load={load} afterLoad={afterLoad} />);
// ... some interaction that requires the `load`
load.succeed(result);
expect(afterLoad).toHaveBeenCalledWith(result);

Тест не проходит, потому что expect() оценивается перед обработчиками связанных обещаний. Мне нужно воспроизвести длину внутренней цепочки обещаний в тесте, чтобы получить то, что мне нужно, например:

return Promise.resolve(load.succeed(result))
  // length of the `.then()` chain needs to be at least as long as in the tested code
  .then(() => {})
  .then(() => expect(result).toHaveBeenCalledWith(result));

Какое ожидаемое поведение?

Я ожидал, что Jest предоставит какой-то API для очистки всех ожидающих обработчиков обещаний, например:

load.succeed(result);
jest.flushAllPromises();
expect(result).toHaveBeenCalledWith(result);

Я пробовал runAllTicks и runAllTimers но безрезультатно.


_ В качестве альтернативы, если мне просто не хватает какой-то уже существующей функции или шаблона, я надеюсь, что кто-то здесь укажет мне правильное направление :) _

Enhancement New API proposal

Самый полезный комментарий

Вспомогательная функция может превратить это в само обещание, поэтому вам не нужно иметь дело с обратным вызовом done. Он достаточно маленький, и его довольно безвредно хранить в пользовательском пространстве, но я бы не стал жаловаться, если бы он был помещен в объект шутки. Что-то подобное часто используется в моих проектах.

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

С async await это почти красиво:

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

Все 46 Комментарий

Хотя асинхронное тестирование обещает, хорошо помнить, что вы можете вернуть тестовую функцию как обещание, поэтому что-то вроде этого будет работать:

test('my promise test', () => { //a test function returning a Promise
  return Promise.resolve(load.succeed(result))
    .then(() => {})
    .then(() => expect(result).toHaveBeenCalledWith(result));
})

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

@thymikee Конечно, я возвращаю значение, чтобы Jest .then(() => {}) . Я не понимаю, как я могу описать проблему более кратко, чем я уже сделал в первом сообщении. Внимательно прочтите его и либо повторно откройте проблему, либо опишите, как ее обойти.

_Я добавил return в код OP, чтобы избежать путаницы.

Столкнулся с аналогичной проблемой и описал ее здесь: https://github.com/pekala/test-problem-example

Вкратце: я пытаюсь утверждать последовательность действий, отправленных в хранилище Redux в результате взаимодействия с пользователем (смоделированного с использованием фермента). Действия как отправленная синхронизация и асинхронность с использованием обещаний (имитируются для немедленного разрешения). Кажется, нет никакого способа утверждать после того, как цепочка обещаний исчерпана, если у вас нет прямого доступа к цепочке обещаний. setTimeout(..., 0) работает, но кажется хакерским, и если утверждение в обратном вызове setTimeout не выполняется, Jest завершается с ошибкой тайм-аута (вместо ошибки утверждения).

Идея flushAllPromises кажется решением, хотя я думаю, что это то, что должен делать runAllTicks ?

В качестве продолжения: я попытался заменить setTimeout(..., 0) на setImmediate и это, похоже, запускает утверждения после того, как очередь микрозадач обратного вызова Promise исчерпана, и предотвращает тайм-аут Jest при ошибках утверждения. Итак, это работает нормально и является приемлемым решением для моего варианта использования:

test('changing the reddit downloads posts', done => {
    setImmediate(() => {
        // assertions...
        done()
    })
})

Вспомогательная функция может превратить это в само обещание, поэтому вам не нужно иметь дело с обратным вызовом done. Он достаточно маленький, и его довольно безвредно хранить в пользовательском пространстве, но я бы не стал жаловаться, если бы он был помещен в объект шутки. Что-то подобное часто используется в моих проектах.

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

С async await это почти красиво:

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

@jwbay , вот и

Это правда, что этот flushPromises оказывается однострочным, но совсем не очевидно, как добраться до этой строки. Поэтому я думаю, что для пользователей Jest было бы полезно иметь его как служебную функцию.

@pekala, один лайнер IMO не обеспечивает требуемого поведения, потому что он не будет ждать, пока не будет выполнено следующее ожидающее обещание:

function foo() {  
  return new Promise((res) => {
    setTimeout(() => {
      res()
    }, 2000);
  });
}

Как насчет swizzling Promise, и когда новый Promise будет создан, добавьте его в какой-либо массив, а затем очистите все обещания, которые будут ожидать на Promise.all по этому массиву?

@talkol Я думаю, что так и будет, если вы также используете фальшивые таймеры. Но я этого не проверял.

@pekala в этом примере нет необходимости подделывать таймеры, поскольку обещание разрешится только по истечении времени
Я просто беспокоюсь, что swizzling Promise испортит шутливую внутреннюю работу, это немного жестко.

Если вы не фальсифицируете таймеры, ваши тесты будут длиться более двух секунд. Я думаю, что лучше всего было бы удалить эти типы задержек, и в этом случае flushPromises предложенный @jwbay, выполнит свою работу.

Все зависит от того, что вы пытаетесь протестировать :) Все, что я говорю, это то, что таймеры не связаны с ожиданием обещаний.

Мы сталкиваемся с проблемами, связанными с нерешаемыми обещаниями, которые перемешиваются с вызовами setTimeout. В jest v19.0.2 у нас нет проблем, но в jest v20.0.0 Promises никогда не входят в функции разрешения / отклонения, и поэтому тесты не проходят. Наша проблема, похоже, связана с проблемой отсутствия _an API для очистки очереди разрешения обещаний_, но эта проблема, похоже, предшествует jest v20.0.0, где мы начали видеть проблему, поэтому я не совсем уверен.

Это единственное решение, которое мы смогли придумать для некоторых из наших тестов, поскольку у нас есть серия чередующихся setTimeout s и Promise s, используемых в коде, который в конечном итоге вызывает onUpdateFailed обратный вызов.

  ReactTestUtils.Simulate.submit(form);
  return Promise.resolve()
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => {
      expect(onUpdateFailed).toHaveBeenCalledTimes(1);
      expect(getErrorMessage(page)).toEqual('Input is invalid.');
    });

Не так красиво, поэтому любые советы здесь очень ценны.

Еще один пример, когда вы не можете вернуть обещание из теста:

describe('stream from promise', () => {
  it('should wait till promise resolves', () => {
    const stream = Observable.fromPromise(Promise.resolve('foo'));
    const results = [];
    stream.subscribe(data => { results.push(data); });
    jest.runAllTimers();
    expect(results).toEqual(['foo']);
  });
});

Этот тест не проходит с шуткой 20.0.4.

Решение @philwhln также можно записать с помощью async / await

ReactTestUtils.Simulate.submit(form);

await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();

expect(onUpdateFailed).toHaveBeenCalledTimes(1);
expect(getErrorMessage(page)).toEqual('Input is invalid.');

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

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

Я тестирую код, который использует Promise.all для обертывания нескольких обещаний. Когда одно из этих обернутых обещаний терпит неудачу (потому что это то, что я хочу проверить), обещание немедленно возвращается, что означает, что иногда другие обещания (состояние гонки, недетерминированное) возвращаются во время выполнения следующего теста.

Это вызывает разного рода хаос, поскольку мои тесты дают непредсказуемые / повторяемые результаты.

Чтобы правильно реализовать это, нам нужно было бы имитировать Promise чтобы мы могли в конечном итоге увидеть все поставленные в очередь микрозадания для их синхронного решения. Что-то вроде того, что делает обещание-имитация .

Уже существует API для сброса микрозадач, поставленных в очередь с помощью process.nextTick и этот API, вероятно, также должен работать с Promises ( jest.runAllTicks ).

У меня было решение с жасмином, которое подключалось к nextTick of Yaku, библиотеке обещаний, перехватывало вызовы nextTick и позволяло проигрывать их раньше.
Однако шутка сама использует обещания, что делает это проблематичным.
В конце концов, я взял Yaku и взломал его, чтобы получить метод flush, очищающий его очередь. По умолчанию он обычно запускается с использованием nextTick, но если вы вызываете flush, выполняются все ожидающие обработчики обещаний.
Источник здесь:
https://github.com/lukeapage/yaku-mock
Можно было бы привести в порядок, связаться с ysmood, чтобы узнать, что они думают об этом, и добавить документацию, но он в значительной степени делает то, что вы хотите, и работал для меня как простое решение для синхронизации обещаний в тестах.

В качестве простого обходного пути мне нравится решение @jwbay .

Как насчет того, чтобы добавить что-то похожее на объект jest ?

await jest.nextTick();

Реализовано как

const nextTick = () => new Promise(res => process.nextTick(res));

cc @cpojer @SimenB @rogeliog

Я использую фермент для монтирования компонентов React.

У меня тоже есть функции, которые ожидают выполнения обещаний, но ни одно из вышеупомянутых исправлений не помогло. Я мог бы обрабатывать их синхронно в моем тесте - если - функции возвращали объекты Promise, используя await , но, к сожалению, функции не возвращают объекты Promise.

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

global.Promise = require.requireActual('promise');

it('my test', async () => {
    const spy = sinon.spy(global, 'Promise');

    wrapper.props().dispatch(functionWithPromiseCalls());

    for (let i = 0; i < spy.callCount; i += 1) {
      const promise = spy.getCall(i);
      await promise.returnValue;
    }

    expect(...)
});

Я столкнулся с вариантом использования для этого (спасибо @jwbay за потрясающую технику)

Например, вы хотите проверить, что у вашей функции есть тайм-аут, и что тайм-аут точно применяется:

      jest.useFakeTimers();
      const EXPECTED_DEFAULT_TIMEOUT_MS = 10000;

      const catchHandler = jest.fn().mockImplementationOnce(err => {
        expect(err).not.toBeNull();
        expect(err.message).toContain('timeout');
      });

      // launch the async func returning a promise
      fetchStuffWithTimeout().catch(catchHandler);

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(EXPECTED_DEFAULT_TIMEOUT_MS - 1);
      await flushPromises();

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(1);
      await flushPromises();

      expect(catchHandler).toHaveBeenCalledTimes(1); // ok, rejected precisely

возврат обещания не позволяет проверить точное время разрешения / отклонения.

Там нужна промывка обещания. Без него ожидание называть рано.

Надеюсь, это поможет сузить проблему.

Для тех, кто следит за вами, здесь есть открытый пиар: # 6876

Перекрестная публикация с https://github.com/airbnb/enzyme/issues/1587

Мне интересно, достаточно ли следующего шаблона для решения этой проблемы, и если я делаю что-то, что считается плохой практикой, и я не должен этого делать.

Что люди думают об этом подходе?

export class MyComponent extends React.Component {
  constructor (props) {
    super(props)

    this.hasFinishedAsync = new Promise((resolve, reject) => {
      this.finishedAsyncResolve = resolve
    })
  }

  componentDidMount () {
    this.doSomethingAsync()
  }

  async doSomethingAsync () {
    try {
      actuallyDoAsync()
      this.props.callback()
      this.finishedAsyncResolve('success')
    } catch (error) {
      this.props.callback()
      this.finishedAsyncResolve('error')
    }
  }

  // the rest of the component
}

А в тестах:

it(`should properly await for async code to finish`, () => {
  const mockCallback = jest.fn()
  const wrapper = shallow(<MyComponent callback={mockCallback}/>)

  expect(mockCallback.mock.calls.length).toBe(0)

  await wrapper.instance().hasFinishedAsync

  expect(mockCallback.mock.calls.length).toBe(1)
})

У меня возникла проблема, когда асинхронный вызов не выполнялся прямо в componentDidMount, но он вызывал асинхронную функцию, которая вызывала другую асинхронную функцию и так далее. Если бы я добавил дополнительный шаг async во всю цепочку async, мне нужно было бы добавить дополнительный .then() или дополнительный await , но это работает нормально.

Есть ли причина, по которой мне не следует использовать этот подход, или это нравится людям?

Я пошел в приключение, сделав это в пользовательской среде, и обнаружил, что это действительно выполнимо и не так уж плохо (хотя есть немало подводных камней, с которыми можно столкнуться, если у вас нет карты). Вот отчет об опыте, который (надеюсь) достаточно подробный, чтобы использовать его напрямую ; TL; DR должен преобразовать async / await в promises и заменить собственные обещания на bluebird и собственные таймеры на lolex; транспилировать все , включая node_modules/ ; queueMicrotask - это примитив, который вам нужен для обещаний, но по умолчанию lolex не предоставляет его из-за того, что JSDOM не предоставляет его.

Я столкнулся с той же проблемой с компонентами jest.mockAllTimers() и React, которые вызывают Promise в componentDidMount() .

Решение из # issuecomment-279171856 элегантно решило проблему.

Нам нужно нечто подобное в официальном Jest API!

Я недавно столкнулся с проблемой при обновлении кучи вещей, она выявила проблему в кучу тестов, когда мы не всегда ждали завершения обещаний. И хотя такие методы, как await new Promise(resolve => setImmediate(resolve)); действительно работали в простых случаях, я обнаружил в своих тестах, что мне пришлось бы запустить их несколько раз, чтобы очистить канал. Это то, что @quasicomputational упомянули в своем исследовании здесь . К сожалению, я не думаю, что есть способ узнать, когда эта труба свободна, не перехватывая обещания по мере их создания. Поэтому я создал небольшую библиотеку для этого ... обещание-шпион . Хотя у меня был один тест, в котором использовались поддельные таймеры, и он не работал с этим ... так что это еще не полностью рабочее решение.

Хотя я также предполагаю, что они могут работать только с async / await all в вашем коде, который будет протестирован, ЕСЛИ они переведены в обещания. Если они не будут преобразованы в обещания, то эта библиотека не сможет подключиться к ним и дождаться их завершения.

У меня была такая же проблема, и я понял:
мы не должны сбрасывать ожидающие обещания, вместо этого мы должны полностью завершить тест, если есть ожидающие обещания.
Таким образом, мы будем вынуждены прервать выполнение ожидающих обещаний внутри тестируемого кода с помощью контроллера прерывания:
https://developers.google.com/web/updates/2017/09/abortable-fetch
Шутить промывочные обещания - все равно, что сказать: «Параллелизм - это сложно, поэтому давайте не будем его тестировать». На самом деле должно быть совсем наоборот.
Поскольку параллелизм сложен, мы должны тестировать его еще больше и вообще не допускать прохождения теста с ожидающими обещаниями.

Учитывая беспорядок при прерывании обещаний в этом вопросе Stackoverflow, ясно, что это (ЕЩЕ) нелегко:
https://stackoverflow.com/a/53933849/373542
Я попытаюсь написать реализацию KISS для прерывания моих обещаний fetch и опубликую здесь результат.

@ giorgio-zamparelli: _ «Параллелизм - это сложно, поэтому давайте не будем его тестировать» _ совершенно не соответствует сути исходного отчета. Проблема связана не с _ ожидающими_ обещаниями, а скорее с тем фактом, что ожидание распространения _ разрешения_ обещания через асинхронный код в тестах излишне сложно.

Я думаю, что смывные обещания вылечили бы симптомы, а не болезнь.

Обещания должны нормально разрешаться в тестах без необходимости сбрасывать их.
Если в вашем тесте есть ожидающее обещание, вы должны либо дождаться его разрешения, используя, например, wait from @testing-library/react ИЛИ, если ожидающее обещание не входит в объем теста, вы должны либо имитируйте код, запускающий его, или вы должны прервать ожидающее обещание где-нибудь, например, в событии жизненного цикла React willUnmount, используя AbortController

AbortController - это новый API, который почти никто не использует, и я чувствую, что это исправление большинства зависающих обещаний в тестах.

ДОКАЖИ, ЧТО Я НЕПРАВ:
Я легко мог бы ошибиться, если бы кто-то, сообщивший о проблемах с нерешенными проблемами в этом выпуске, уже пытался использовать AbortController и jest.mock, но этого было недостаточно.

@ giorgio-zamparelli: Может быть, недоразумение связано с тем, что я использовал фразу _ "очистить все ожидающие обработчики обещаний" _ (и если это так, мне очень жаль). Как вы, надеюсь, увидите, если внимательно прочитаете описание проблемы, я имел в виду «ожидающие обработчики обещаний».

Итак, повторюсь, мы не говорим здесь о _ ожидающих_ обещаниях (в любом случае), а скорее о сбрасывании разрешения обещаний с минимальными хлопотами. Или, другими словами, о прозрачном и детерминированном переходе от точки, где выполняется обещание, к точке, где вызываются все последующие связанные с ним эффекты (чтобы мы могли проверить результат этого).

Недавно я выпустил для этой цели flush-microtasks . Он заимствует его реализацию от React, которая на удивление более сложным , чем @jwbay «s решение здесь или @thymikee» s решение здесь . Я не уверен, имеет ли сложность какую-либо значительную разницу, но я предполагаю, что она учитывает крайние случаи, не учитываемые другими решениями в этом потоке. Я использовал эту реализацию только потому, что react-testing-library использует ее (см. Здесь ), но не раскрывает ее.

import { flushMicroTasks } from 'flush-microtasks'

await flushMicroTasks()

@aleclarson Есть ли разница между flush-microtasks и flush-promises?

@ramusus Похоже, flush-promises использует тот же подход, что и решение @jwbay .

https://github.com/kentor/flush-promises/blob/46f58770b14fb74ce1ff27da00837c7e722b9d06/index.js

RTL также скопировал код React: https://github.com/testing-library/react-testing-library/blob/8db62fee6303d16e0d5c933ec1fab5841dd2109b/src/flush-microtasks.js

РЕДАКТИРОВАТЬ: ха, уже упоминалось: ухмылка:

Я не уверен, что нам нужно встроить это в Jest, когда люди могут это использовать? Может, ссылку на него можно найти в документации? Эта проблема связана с их синхронной очисткой, что, я думаю, выходит за рамки того, что мы хотим сделать (тем более, что это невозможно с async-await ).

Решение flushPromises работает только с обещаниями, которые разрешаются немедленно, но не с теми, которые все еще ожидают выполнения.

Хм, хороший момент. Я не знаю, можно ли как-нибудь отследить обещания pending . Возможно, удастся сделать что-нибудь умное с async_hooks , не уверен. Наверное, будет больно пытаться различать обещания, созданные кодом пользовательского пространства, и обещания, созданные Jest и его зависимостями.

Я пытался обернуть / издеваться над объектом Promise чтобы включить счетчик, но это не сработало:

const _promise = window.Promise;
window.Promise = function(promiseFunction){
    // counter
    return new _promise(promiseFunction);
}

Основная проблема - это async функции, которые вообще не используют глобальный Promise

Хорошо, я нашел действительно хакерский способ, подобный этому .

  1. Создайте новый модуль со списком.
  2. Добавьте свои обещания в этот список.
  3. Разрешите обещания в своем тесте и удалите их из списка.
  4. В моем случае я запускаю wrapper.update() из фермента. При необходимости сделайте что-нибудь подобное.
  5. Повторяйте шаги 3 и 4, пока список не станет пустым.

Я знаю, что корректировать код под тесты - не лучшая практика, НО я использую эту логику уже при рендеринге на стороне сервера. Но в конце концов это просто ожидание. ¯ \ _ (ツ) _ / ¯

В Jest 26 есть интересное обновление, где поддельные таймеры теперь основаны на @ sinon / fake-timers (если они включены с помощью jest.useFakeTimers('modern') ).

Я пробовал современные фальшивые таймеры с моими тестами, и, к сожалению, это приводит к тому, что взлом await new Promise(resolve => setImmediate(resolve)); зависает на неопределенное время. К счастью, @sinon/fake-timers включает в себя несколько методов *Async() которые «также прерывают цикл событий, позволяя любым запланированным обратным вызовам обещаний выполняться _ перед_ запуском таймеров». К сожалению, я не вижу способа получить объект clock через Jest API.

Кто-нибудь знает, как заставить Jest передать нам этот объект clock ?

Как и другие, моя мотивация использовать await new Promise(setImmediate); состоит в том, чтобы сбросить разрешимые обещания, чтобы я мог провести модульное тестирование их воздействия на систему.

Казалось бы, «современные» фальшивые таймеры действительно отстают от других из-за бессмысленного тайм-аута.

Вот несколько модульных тестов, чтобы описать это:

describe('flushing of js-queues using different timers', () => {
  beforeAll(() => {
    // It would take the failing test 5 long seconds to time out.
    jest.setTimeout(100);
  });

  it.each([
    [
      'given real timers',
      () => {
        jest.useRealTimers();
      },
    ],
    ['given no timers', () => {}],
    [
      'given "legacy" fake timers',
      () => {
        jest.useFakeTimers('legacy');
      },
    ],
    [
      // This is the the failing scenario, not working like the other timers.
      'given "modern" fake timers',
      () => {
        jest.useFakeTimers('modern');
      },
    ],
  ])(
    '%s, when using setImmediate to flush, flushes a promise without timing out',
    async (_, initializeScenarioSpecificTimers) => {
      initializeScenarioSpecificTimers();

      let promiseIsFlushed = false;

      Promise.resolve().then(() => {
        promiseIsFlushed = true;
      });

      // Flush promises
      await new Promise(setImmediate);

      expect(promiseIsFlushed).toBe(true);
    },
  );
});

Я чувствую, что предыдущий тест не должен провалиться.

Для меня обходной путь состоял в том, чтобы сбросить обещания с помощью нодового "setImmediate" из пакетных "таймеров" вместо глобального "setImmediate". После этого проходит следующее:

import { setImmediate as flushMicroTasks } from 'timers';

it('given "modern" fake timers, when using native timers to flush, flushes a promise without timing out', async () => {
  jest.useFakeTimers('modern');

  let promiseIsFlushed = false;

  Promise.resolve().then(() => {
    promiseIsFlushed = true;
  });

  // Flush micro and macro -tasks
  await new Promise(flushMicroTasks);

  expect(promiseIsFlushed).toBe(true);
});

Спасибо @aleclarson.

Вот наше решение этой проблемы:

https://github.com/team-igniter-from-houston-inc/async-fn
https://medium.com/houston-io/how-to-unit-test-asynchronous-code-for-javascript-in-2020-41c124be2552

Тестовый код можно записать так:

// Note: asyncFn(), extends jest.fn() with a way to control resolving/rejecting of a promise
const load = asyncFn();

const afterLoad = jest.fn();
const result = 'mock result';

mount(<Component load={load} afterLoad={afterLoad} />);

// ... some interaction that requires the `load`

// Note: New way to controlling when promise resolves
await load.resolve(result);

expect(afterLoad).toHaveBeenCalledWith(result);

Обратите внимание, что вам не нужно ничего знать о сбрасывании обещаний или запуске таймеров.

@jansav nice / + 1. Fwiw я видел этот подход, называемый отложенным шаблоном. Я думаю, это делает тесты лучше.

Мне кажется, что проблема с фальшивыми таймерами заключается в том, что они нарушают естественный цикл выполнения того, как таймеры должны работать. Интересно, почему мы не можем просто сделать так, чтобы функции запуска таймера шутки были асинхронными? Изменение таймеров для синхронного разрешения действительно делает тестовый код аккуратным, но вызывает этот массивный побочный эффект.

мой вариант использования:

public static resolvingPromise<T>(result: T, delay: number = 5): Promise<T> {
    return new Promise((resolve) => {
        setTimeout(
            () => {
                resolve(result);
            },
            delay
        );
    });
}

тестовый файл:

it("accepts delay as second parameter", async () => {
    const spy = jest.fn();
    MockMiddleware.resolvingPromise({ mock: true }, 50).then(spy);
    jest.advanceTimersByTime(49);
    expect(spy).not.toHaveBeenCalled();
    jest.advanceTimersByTime(1);
    await Promise.resolve(); // without this line, this test won't pass
    expect(spy).toHaveBeenCalled();
});
Была ли эта страница полезной?
0 / 5 - 0 рейтинги