Автор оригинала: José Proença.
Проблема
Я нашел два основных подхода, когда единичное тестирование наблюдается поведение в JavaScript:
- МАРМИЛЬНЫЕ ИССЛЕДОВАНИЯ СМАРЫ для сравнения наблюдаемого для издевателяшего наблюдаемого
- Проверка наблюдаемого напрямую время контроля с Zone.js
fakeasync ()
Отказ Проверьте это Отличная статья От Виктора Савкина оfakeasync ()
Отказ
Zone.js fakeasync ()
Перехватывает асинхронные функции JavaScript, позволяющие контролировать время. Итак, наблюдаемые не нуждаются в модификации и, по умолчанию, они используют планировщик по умолчанию, а не Testscheduler
требуется структурой тестирования мрамора. Хотя можно сделать наблюдаемые в использовании прилагаемого планировщика, он несколько громоздкий, и у меня было много проблем с этим маршрутом. Поэтому я решил проверить наблюдаемые непосредственно.
Также произошла эта проблема с рамками тестирования мрамора. Для тестирования оператора является потрясающим и очень простым, но он не является адекватным для тестирования реального наблюдаемого с его своевременным выбросом значений, поскольку сравнение с издевателями наблюдаемого также учитывает время, когда значения испускаются. И поскольку, возможно, нет необходимости утверждать время между выбросами, сравнивая наблюдаемые выбросы стоимости до массива значений, адекватны.
Решение
Решение – это просто функция для реализации сравнения между наблюдаемым и массивом значений, создавая обещание, которое решает, если есть совпадение или отклонение, если нет. Он имеет следующую подпись:
function matchObservable( obs$: Observable , values: Array , expectComplete: boolean = true, expectError: boolean = false, matcher: (actual: T, expected: T) => boolean = (a, b) => a === b, ): Promise
Это сравнивает значения, которые наблюдатель производит с предусмотренным массивом значений. Это также проверяет наблюдаемое завершение или ошибка, если требуется.
Применение
Я проиллюстрирую использование на примере жасминового теста для службы генератора таймера. Сервис генерирует наблюдаемый, который делает обратный отсчет, затем завершится:
let duration = 5; // in seconds return Observable .interval(1000) .map(i => duration - i - 1) .take(duration) .startWith(duration);
Тестовый код прост, он пытается сопоставить сгенерированную последовательность к массиву значений, используя matchobservable ()
Отказ
it('should generate a timer', fakeAsync(() => { const expectedValues = [5, 4, 3, 2, 1, 0]; const timer$ = service.getTimer(5); let matchResult: string; matchObservable(timer$, expectedValues, true) .then(() => matchResult = null, (result) => matchResult = result); tick(10000); expect(matchResult).toBeNull(); }));
Обратите внимание на Отметьте (10000)
Отказ Необходимо провести время своевременно поведение наблюдателя (как в Интервал (1000)
). Кроме того, разрешение или отклонение обещания влияет на локальную переменную Matchresult
который используется для утверждения матча. Это делает асинхронный код для последовательного запуска, и тестовый поток становится «плоским», изготовлением композиции различных шагов утверждения, простым.
Если наблюдатель теста не использует какие-либо конкретные синхронизации, а не Tick ()
, используйте Flush ()
или mplymicrotasks ()
продвигать асинхронные ожидающие задачи.
Сотрудничество функция
matchobservable ()
Функция подписывается при условии предоставленного наблюдателя и, используя предоставленный сопоставитель, по сравнению со значениями исходных значений наблюдается со значениями в прилагаемом массиве.
export function matchObservable( obs$: Observable , values: Array , expectComplete: boolean = true, expectError: boolean = false, matcher: (actual: T, expected: T) => boolean = (a, b) => a === b, ): Promise { return new Promise (matchObs); function matchObs(resolve: () => void, reject: (reason: any) => void) { let expectedStep = 0; const subs: Subscription = obs$.subscribe({ next, error, complete }); return; } }
Как и в случае с любой подпиской, необходимо учитывать осторожность, если наблюдатель горячий или холодный. Если жарко, убедитесь, что вы звоните matchobservable ()
Прежде чем наблюдатель запускает излучение значений, которые вы хотите сравнить с массивом. Если холодно, позаботьтесь о возможных побочных эффектах, вызванных повторным выбросом значений наблюдателя. Вы можете прочитать больше о горячем и холодном наблюдателях на Статья Бен Лес Отказ
Подписка и соответствующая отписаться в функцию. И поскольку мы имеем дело с асинхронным кодом (обещание и наблюдаемое) особое внимание было принято. Это сосредоточено на этой функции помощника:
function finalize(message?: string) { expectedStep = -1; setTimeout(() => subs.unsubscribe(), 0); if (message) reject(message); else resolve(); }
Отписаться ()
Производится только на следующем тике (выполненном с Settimeate ()
Поскольку в некоторых обстоятельствах некоторые обработчики подписки работают до того, как OBS $ .subscribe ()
возвращает и хранит значение по локальной переменной Subs
. В этих случаях, и если эта функция вызывается на том первом вызове обработчика, вариабельный поднос будет нулевым в то время финализировать ()
.
Кроме того, при разрешении или отклонении локальная переменная Представленные
Должен быть признан недействительным и таким образом охраняет любой обработчик для запуска значительного кода после того, как обещание разрешено или отклонено. Охранник необходим, потому что другой обработчик можно назвать в том же тике, что разрешение/отклонение обработчика, но до Отписаться.
«Следующий» обработчик
Используя предоставленный сопоставитель, «Следующий» обработчик отправляется по сравнению от испускаемых значений наблюдателя к предоставленному массиву значений. Отказывает обещание, если нахождение каких-либо несоответствий или решается, когда отделки сравнивает массив ценностей.
function next(value) { if (expectedStep === -1) return; if (expectedStep >= values.length) finalize('Too many values on observable: ' + JSON.stringify(value)); else { if (matcher(value, values[expectedStep]) === false) finalize('Values are expected to match: ' + JSON.stringify(value) + ' and ' + JSON.stringify(values[expectedStep])); else { expectedStep++; if (!expectComplete && !expectError && expectedStep === values.length) finalize(); } } }
«Ошибка» и «полные» обработчики
Эти обработчики действительно просты и аналогичные, решив решить или отклонить обещание.
Обратите внимание, что с ожидаемоеменее
и EnvereRor
И ЛОЖЬ, возвращенное обещание разрешено как только массив значений соответствует. Остальная часть поведения наблюдателя не наблюдается. Если оба флага верны, функция совпадает с полной или ошибкой.
function error(error) { if (expectedStep === -1) return; if (expectError && expectedStep === values.length) finalize(); else finalize('Observable errored unexpectedly. Error: ' + error.toString()); } function complete() { if (expectedStep === -1) return; if (expectedStep === values.length) finalize(); else finalize(`Observable completed unexpectedly after ${expectedStep} value emissions. ` + (expectedStep < values.length ? 'Missing values from observable.' : 'Too many values on observable.')); }
Полный код на Github Repository Отказ Это также на NPM Отказ
Повеселись! PS: эта статья также на мой блог Отказ