Угловая: Тэставанне асінхронных рэчаў у зоне fakesAsync VS. прадастаўленне карыстацкіх планавальнікаў

Мне шмат разоў задавалі пытанні наконт "падробленай зоны" і як ёй карыстацца. Менавіта таму я вырашыў напісаць гэты артыкул, каб падзяліцца сваімі назіраннямі, калі справа даходзіць да дробназярністых тэстаў "fakeAsync".

Зона з'яўляецца найважнейшай часткай вуглавой экасістэмы. Можна было прачытаць, што сама зона - гэта своеасаблівы "кантэкст выканання". На самай справе, Angular Monkeypatches глабальных функцый, такіх як setTimeout або setInterval, каб перахапіць функцыі, якія выконваюцца пасля пэўнай затрымкі (setTimeout) або перыядычна (setInterval).

Важна адзначыць, што ў гэтым артыкуле не будзе паказана, як змагацца з хакерамі setTimeout. Паколькі Angular актыўна выкарыстоўвае RxJs, што абапіраецца на родныя функцыі сінхранізацыі (вы можаце быць здзіўлены, але гэта праўда), ён выкарыстоўвае зону як складаны, але магутны інструмент для запісу ўсіх асінхронных дзеянняў, якія могуць паўплываць на стан прыкладання. Вуглавыя іх перахоплівае, каб даведацца, ці ёсць яшчэ нейкая праца ў чарзе. Ён асушае чэргі ў залежнасці ад часу. Хутчэй за ўсё, ачышчаныя задачы змяняюць значэнні кампанентаў зменных. У выніку шаблон будзе паўторна выказаны.

Цяпер усе асінхронныя матэрыялы - гэта не тое, пра што трэба хвалявацца. Гэта проста прыемна зразумець, што адбываецца пад капотам, таму што гэта дапамагае напісаць эфектыўныя адзінкавыя тэсты. Акрамя таго, распрацоўка тэстаў аказвае велізарны ўплыў на зыходны код ("паходжанне TDD было жаданне атрымаць моцную аўтаматычную рэгрэсійную праверку, якая падтрымлівала эвалюцыйны дызайн. Па шляху яе практыкі выявілі, што напісанне тэстаў спачатку значна палепшыла працэс распрацоўкі. "Марцін Фаўлер, https://martinfowler.com/articles/mocksArentStubs.html, 09/2017).

У выніку ўсіх гэтых намаганняў мы можам зрушыць час, як нам трэба праверыць на стан у пэўны момант часу.

fakeAsync / контур галачкі

У куцевых дакументах гаворыцца, што fakeAsync (https://angular.io/guide/testing#fake-async) дае больш лінейны вопыт кадавання, паколькі ён пазбаўляецца ад абяцанняў, такіх як .whenStable (). Тады (…).

Код ўнутры блока fakeAsync выглядае так:

галачка (100); // Чакаць першага задання, якое трэба зрабіць
fixture.detectChanges (); // Абнавіць прагляд з цытатай
галачка (); // Чакаць, калі будзе выканана другая задача
fixture.detectChanges (); // Абнавіць прагляд з цытатай

Наступныя фрагменты даюць некаторы ўяўленне пра тое, як працуе fakeAsync.

Тут выкарыстоўваецца setTimeout / setInterval, таму што яны выразна паказваюць, калі выконваюцца функцыі ў зоне fakeAsync. Вы можаце чакаць, што гэтая функцыя "яна" павінна ведаць, калі тэст зроблены (у Жасмін, арганізаваны па аргуменце: Функцыя), але на гэты раз мы разлічваем на спадарожніка fakeAsync, а не на выкарыстанне зваротнага званка:

it ('асушае задачу зоны па заданні', fakeAsync (() => {
        setTimeout (() => {
            няхай i = 0;
            const handle = setInterval (() => {
                калі (я ++ === 5) {
                    clearInterval (ручка);
                }
            }, 1000);
        }, 10000);
}));

Ён гучна скардзіцца, бо ў чарзе застаюцца некаторыя “таймеры” (= setTimeouts):

Памылка: у тайме яшчэ застаецца 1 таймер.

Відавочна, што нам трэба зрушыць час, каб выканаць часовую функцыю. Мы дадаем параметрызаваны "галачку" на працягу 10 секунд:

кляшчоў (10000);

Х'ю? Памылка становіцца больш заблытанай. Цяпер тэст не праходзіць з-за запушчанага "перыядычных таймераў" (= setIntervals):

Памылка: у чарзе яшчэ застаюцца 1 перыядычны таймер.

Паколькі мы ўвайшлі ў функцыю, якую трэба выконваць кожную секунду, нам таксама трэба зрушыць час, выкарыстоўваючы галачку яшчэ раз. Функцыя спыняецца праз 5 секунд. Вось чаму нам трэба дадаць яшчэ 5 секунд:

цік (15000);

Цяпер тэст праходзіць. Варта сказаць, што зона распазнае задачы, якія працуюць паралельна. Проста пашырыце часовую функцыю з дапамогай іншага выкліку setInterval.

it ('асушае задачу зоны па заданні', fakeAsync (() => {
    setTimeout (() => {
        няхай i = 0;
        const handle = setInterval (() => {
            калі (++ i === 5) {
                clearInterval (ручка);
            }
        }, 1000);
        няхай j = 0;
        const handle2 = setInterval (() => {
            калі (++ j === 3) {
                clearInterval (handle2);
            }
        }, 1000);
    }, 10000);
    цік (15000);
}));

Тэст усё яшчэ праходзіць, таму што абодва гэтыя мноства пачаліся ў той жа момант. Абодва яны зроблены, калі прайшло 15 секунд:

fakeAsync / галачка ў дзеянні

Цяпер мы ведаем, як працуе матэрыял fakeAsync / цікаў. Дазвольце гэта выкарыстоўваць для нейкіх змястоўных рэчаў.

Давайце распрацуем поле, падобнае на прапановы, якое адпавядае гэтым патрабаванням:

  • ён атрымлівае вынік ад нейкага API (службы)
  • ён спыняе ўвод карыстальніка для чакання канчатковага тэрміна пошуку (памяншае колькасць запытаў); DEBOUNCING_VALUE = 300
  • ён паказвае вынік у карыстацкім інтэрфейсе і выдае адпаведнае паведамленне
  • тэст прылады паважае асінхронны характар ​​кода і правярае належнае паводзіны падобнага на поле ў плане праходжання часу

Мы ў канчатковым выніку з гэтымі сцэнарыямі тэставання:

description ('пры пошуку', () => {
    гэта ('ачышчае папярэдні вынік', fakeAsync (() => {
    }));
    гэта ('выпраменьвае стартавы сігнал', fakeAsync (() => {
    }));
    гэта ("апускае магчымыя хіты API на 1 запыт у DEBOUNCING_VALUE мілісекунд", fakeAsync (() => {
    }));
});
description ("на поспех", () => {
    it ('выклікае API Google ", fakeAsync (() => {
    }));
    гэта ('выпраменьвае сігнал аб поспеху з колькасцю супадзенняў', fakeAsync (() => {
    }));
    гэта ('паказвае загалоўкі ў полі прапановы', fakeAsync (() => {
    }));
});
description ('па памылцы', () => {
    гэта ('выпраменьвае сігнал пра памылку', fakeAsync (() => {
    }));
});

У раздзеле "Пошук" мы не чакаем вынікаў пошуку. Калі карыстальнік падае ўвод (напрыклад, "Lon"), папярэднія параметры трэба ачысціць. Мы чакаем, што варыянты будуць пустымі. Дадаткова неабходна ўключыць увод карыстальніка, скажам, у 300 мілісекунд. Што тычыцца зоны, у чарзе выштурхоўваецца мікразадача ў 300 мільёнаў.

Звярніце ўвагу, што я апускаю некаторыя дэталі для сцісласці:

  • Настройка тэсту амаль такая ж, як і ў кутніх дакументах
  • экзэмпляр apiService ўводзіцца праз fixture.debugElement.injector (...)
  • SpecUtils запускае звязаныя з карыстальнікам падзеі, такія як ўваход і фокус
beforeEach (() => {
    spyOn (apiService, 'запыт'). and.returnValue (Observable.of (queryResult));
});
fit ('ачышчае папярэдні вынік', fakeAsync (() => {
    comp.options = ['не пуста'];
    SpecUtils.focusAndInput ("Lon", прыстасаванне, "ўваход");
    галачка (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    чакаць (comp.options.length) .toBe (0, `было [$ {comp.options.join (',')}]`);
}));

Код кампанента спрабуе задаволіць тэст:

ngOnInit () {
    this.control.valueChanges.debounceTime (300). падпісацца (значэнне => {
        this.options = [];
        this.suggest (значэнне);
    });
}
прапанаваць (q: string) {
    this.googleBooksAPI.query (q). падпісацца (вынік => {
// ...
    }, () => {
// ...
    });
}

Праглядзім код крок за крокам:

Мы шпіёнім за метадам запыту apiService, які мы збіраемся выклікаць у кампаненце. Пераменная queryResult змяшчае некаторыя макетныя дадзеныя, такія як "Гамлет", "Макбет" і "Кінг Лір". У пачатку мы чакаем, што параметры будуць пустымі, але, як вы маглі заўважыць, усю чаргу fakeAsync зліваецца галачкай (DEBOUNCING_VALUE), і таму кампанент змяшчае канчатковы вынік працы Шэкспіра:

Чакаецца, што 3 будзе 0, "было [Гамлет, Макбет, Кінг Лір]".

Нам патрэбна затрымка з запытам на паслугу, каб пераймаць асінхроннае праходжанне часу, затрачанага на выклік API. Дадамо 5 секунд затрымкі (REQUEST_DELAY = 5000) і пастаўце галачку (5000).

beforeEach (() => {
    spyOn (apiService, 'запыт'). and.returnValue (назіраны.of (queryResult) .delay (1000));
});

fit ('ачышчае папярэдні вынік', fakeAsync (() => {
    comp.options = ['не пуста'];
    SpecUtils.focusAndInput ("Lon", прыстасаванне, "ўваход");
    галачка (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    чакаць (comp.options.length) .toBe (0, `было [$ {comp.options.join (',')}]`);
    галачка (REQUEST_DELAY);
}));

На мой погляд, гэты прыклад павінен працаваць, але Zone.js сцвярджае, што ў чарзе яшчэ ёсць нейкая праца:

Памылка: у чарзе яшчэ застаюцца 1 перыядычны таймер.

У гэты момант нам трэба паглыбіцца, каб убачыць тыя функцыі, якія мы падазраем, што яны затрымаліся ў зоне. Усталяванне некаторых пунктаў прарыву - гэта шлях:

адладка зоны fakeAsync

Затым выдайце гэта ў камандным радку

_fakeAsyncTestZoneSpec._scheduler._schedulerQueue [0] .args [0] [0]

альбо вывучыць змест такой зоны:

хммм, метад прамывання AsyncScheduler па-ранейшаму ў чарзе ... чаму?

Назва функцыі, якую запускаюць, - гэта метад прамывання AsyncScheduler.

public flush (дзеянне: AsyncAction ): void {
  const {дзеянні} = гэта;
  калі (this.active) {
    Actions.push (дзеянне);
    вяртанне;
  }
  няхай памылка: любая;
  this.active = праўда;
  рабіць {
    калі (памылка = action.execute (action.state, action.delay)) {
      разбіць;
    }
  } у той час (action = Actions.shift ()); // Вычарпаць чаргі планавання
  this.active = хлусня;
  калі (памылка) {
    у той час (action = Actions.shift ()) {
      action.unsubscribe ();
    }
    памылка кідка;
  }
}

Цяпер вы можаце задацца пытаннем, што не так з зыходным кодам або самой зонай.

Праблема ў тым, што зона і нашы кляшчы не ў сінхранізацыі.

Сама зона мае бягучы час (2017), тады як галачка хоча апрацаваць дзеянне, запланаванае на 1.01.1970 + 300 міліс + 5 секунд.

Значэнне планавальніка async пацвярджае, што:

import {async as AsyncScheduler} з 'rxjs / planer / async';
// Змесціце гэта дзесьці ўнутры "it"
console.info (AsyncScheduler.now ());
// → 1503235213879

AsyncZoneTimeInSyncKeeper на дапамогу

Адзін з магчымых выпраўленняў - гэта ўтыліта захавання ў сінхранізацыі, падобная да гэтай:

клас экспарту AsyncZoneTimeInSyncKeeper {
    час = 0;
    канструктар () {
        spyOn (AsyncScheduler, 'зараз'). and.callFake (() => {
            / * tslint: адключыць наступны радок * /
            console.info ('час', this.time);
            вярнуць this.time;
        });
    }
    галачка (час ?: колькасць) {
        калі (typeof time! == 'undefined') {
            this.time + = час;
            галачка (this.time);
        } яшчэ {
            галачка ();
        }
    }
}

Ён адсочвае бягучы час, які вяртаецца now () кожны раз, калі выкліканы планавальнік async. Гэта працуе, таму што функцыя tick () выкарыстоўвае той самы бягучы час. Абодва, і планіроўшчык, і зона, падзяляюць адзін і той жа час.

Я рэкамендую стварыць timeInSyncKeeper на этапе beforeEach:

description ('пры пошуку', () => {
    хай timeInSyncKeeper;
    beforeEach (() => {
        timeInSyncKeeper = новы AsyncZoneTimeInSyncKeeper ();
    });
});

Зараз давайце паглядзім на выкарыстанне захавальніка часу сінхранізацыі. Майце на ўвазе, што нам трэба заняцца гэтай праблемай, таму што тэкставае поле знятае, і запыт займае некаторы час.

description ("у пошуку", () => {
    хай timeInSyncKeeper;
    beforeEach (() => {
        timeInSyncKeeper = новы AsyncZoneTimeInSyncKeeper ();
        spyOn (apiService, 'запыт'). and.returnValue (Observable.of (queryResult) .delay (REQUEST_DELAY));
    });
    гэта ('ачышчае папярэдні вынік', fakeAsync (() => {
        comp.options = ['не пуста'];
        SpecUtils.focusAndInput ("Lon", прыстасаванне, "ўваход");
        timeInSyncKeeper.tick (DEBOUNCING_VALUE);
        fixture.detectChanges ();
        чакаць (comp.options.length) .toBe (0, `было [$ {comp.options.join (',')}]`);
        timeInSyncKeeper.tick (REQUEST_DELAY);
    }));
    // ...
});

Давайце пройдзем гэты прыклад радок за радком:

  1. стварыць асобнік экзэмпляра захавальніка сінхранізацыі
timeInSyncKeeper = новы AsyncZoneTimeInSyncKeeper ();

2. Дазвольце адказаць метад apiService.query з вынікам queryResult пасля таго, як REQUEST_DELAY прайшоў. Скажам, метад запыту павольны і адказвае праз REQUEST_DELAY = 5000 мілісекунд.

spyOn (apiService, 'запыт'). and.returnValue (Observable.of (queryResult) .delay (REQUEST_DELAY));

3. Зрабіце выгляд, што ў поле прапаноў ёсць опцыя ‚не пустая '

comp.options = ['не пуста'];

4. Перайдзіце ў поле "input" у роднай элеменце прыбора і ўстаўце значэнне "Lon". Гэта імітуе ўзаемадзеянне карыстальніка з полем уводу.

SpecUtils.focusAndInput ("Lon", прыстасаванне, "ўваход");

5. Дазвольце прайсці перыяд часу DEBOUNCING_VALUE у фальшывай зоне асінхроўкі (DEBOUNCING_VALUE = 300 мілісекунд).

timeInSyncKeeper.tick (DEBOUNCING_VALUE);

6. Вызначце змены і паўторна выкладзіце шаблон HTML.

fixture.detectChanges ();

7. Масіў параметраў зараз пусты!

чакаць (comp.options.length) .toBe (0, `было [$ {comp.options.join (',')}]`);

Гэта азначае, што назіраныя valueChanges, якія выкарыстоўваюцца ў кампанентах, удалося запусціць у належны час. Звярніце ўвагу, што выкананая функцыя debounceTime-d

value => {
    this.options = [];
    this.onEvent.emit ({сігнал: SuggestSignal.start});
    this.suggest (значэнне);
}

выштурхнуў іншую задачу ў чаргу, выклікаўшы метад прапанаваць:

прапанаваць (q: string) {
    калі (! q) {
        вяртанне;
    }
    this.googleBooksAPI.query (q). падпісацца (вынік => {
        калі (вынік) {
            this.options = result.items.map (item => item.volumeInfo);
            this.onEvent.emit ({сігнал: SuggestSignal.success, totalItems: result.totalItems});
        } яшчэ {
            this.onEvent.emit ({сігнал: SuggestSignal.success, totalItems: 0});
        }
    }, () => {
        this.onEvent.emit ({сігнал: SuggestSignal.error});
    });
}

Проста ўспомніце шпіёна на метад запыту API Google google, які адказвае праз 5 секунд.

8. Нарэшце, мы павінны паставіць галачку яшчэ раз на працягу REQUEST_DELAY = 5000 мілісекунд, каб прамыць зону ў чарзе. Для назірання, на які мы падпісваемся, у метадзе прапанаваць трэба REQUEST_DELAY = 5000, каб завяршыць.

timeInSyncKeeper.tick (REQUEST_DELAY);

fakeAsync…? Чаму? Ёсць планіроўшчыкі!

Эксперты ReactiveX могуць сцвярджаць, што мы маглі б выкарыстоўваць планавальнікі тэстаў, каб зрабіць назіранне праверкай. Гэта магчыма для вуглавых прыкладанняў, але ў яго ёсць некаторыя недахопы:

  • гэта патрабуе, каб вы азнаёміліся з унутранай структурай назіранняў, аператарамі, ...
  • што рабіць, калі ў вашым дадатку ёсць непрыгожыя шляхі вырашэння setTimeout? Яны не спраўляюцца з планіроўшчыкамі.
  • самае галоўнае: я ўпэўнены, што вы не хочаце выкарыстоўваць планіроўшчыкі ва ўсім сваім дадатку. Вы не хочаце змешваць вытворчы код з тэстамі прылад. Вы не хочаце рабіць нешта падобнае:
const testScheduler;
калі (Environment.test) {
    testScheduler = новы YourTestScheduler ();
}
няхай назіраецца;
калі (testScheduler) {
    opažav = Observable.of ('value'). Затрымка (1000, testScheduler)
} яшчэ {
    назіраецца = Observable.of ('value'). затрымка (1000);
}

Гэта не прымальнае рашэнне. На мой погляд, адзіным магчымым рашэннем з'яўляецца "ўвядзенне" планавальніка тэстаў, прадастаўленне свайго роду "проксі" для рэальных метадаў Rxjs. Варта прыняць да ўвагі і тое, што асноўныя метады могуць негатыўна паўплываць на астатнія адзінкі тэстаў. Менавіта таму мы будзем выкарыстоўваць шпіёны Жасмін. Шпіёны выдаляюцца пасля кожнага.

Функцыя monkeypatchScheduler абгортвае арыгінальную рэалізацыю Rxjs з дапамогай шпіёна. Шпіён прымае аргументы метаду і пры неабходнасці дадае testScheduler.

import {IScheduler} з 'rxjs / Scheduler';
import {Observable} з 'rxjs / Observable';
абвясціць var spyOn: Функцыя;
экспартная функцыя monkeypatchScheduler (планавальнік: IScheduler) {
    хай opaservableMethods = ['concat', 'defer', 'empty', 'forkJoin', 'if', 'interval', 'merge', 'of', 'range', 'кінуць',
        'маланкі]];
    хай operatorMethods = ['буфер', 'concat', 'затрымка', 'выразна', 'do', 'кожны', 'last', 'merge', 'max', 'take',
        'timeInterval', 'lift', 'debounceTime'];
    хай injectFn = функцыя (база: любы, метады: string []) {
        method.forEach (method => {
            const orig = база [метад];
            if (typeof orig === 'function') {
                spyOn (база, метад) .and.callFake (функцыя () {
                    хай args = Array.prototype.slice.call (аргументы);
                    калі (args [args.length - 1] && typeof args [args.length - 1] .now === 'function') {
                        args [args.length - 1] = планавальнік;
                    } яшчэ {
                        args.push (планіроўшчык);
                    }
                    вярнуцца orig.apply (гэта, аргументы);
                });
            }
        });
    };
    injectFn (назіральныя, назіраныя метады);
    injectFn (Observable.prototype, operatorMethods);
}

З гэтага часу testScheduler будзе выконваць усю працу ў Rxjs. Ён не выкарыстоўвае setTimeout / setInterval або нейкія асінхронныя рэчы. У fakeAsync больш няма неабходнасці.

Цяпер нам патрэбны экзамен планіроўшчыка тэсту, які мы хочам перадаць monkeypatchScheduler.

Ён паводзіць сябе вельмі падобна да TestScheduler па змаўчанні, але ён забяспечвае метад зваротнага выкліку onAction. Такім чынам, мы ведаем, якія дзеянні былі выкананы праз які прамежак часу.

клас экспарту SpyingTestScheduler пашырае VirtualTimeScheduler {
    spyFn: (actionName: string, затрымка: лік, памылка ?: любы) => void;
    канструктар () {
        супер (VirtualAction, defaultMaxFrame);
    }
    onAction (spyFn: (actionName: string, затрымка: лік, памылка ?: любы) => void) {
        this.spyFn = spyFn;
    }
    флэш () {
        const {дзеянні, maxFrames} = гэта;
        няхай памылка: любая, дзеянне: AsyncAction ;
        while ((action = Actions.shift ()) && (this.frame = action.delay) <= maxFrames) {
            хай stateName = this.detectStateName (дзеянне);
            хай адтэрмінаваць = action.delay;
            калі (памылка = action.execute (action.state, action.delay)) {
                калі (this.spyFn) {
                    this.spyFn (stateName, затрымка, памылка);
                }
                разбіць;
            } яшчэ {
                калі (this.spyFn) {
                    this.spyFn (stateName, затрымка);
                }
            }
        }
        калі (памылка) {
            у той час (action = Actions.shift ()) {
                action.unsubscribe ();
            }
            памылка кідка;
        }
    }
    прыватнае detectStateName (дзеянне: AsyncAction ): string {
        const c = Object.getPrototypeOf (action.state) .constructor;
        const argsPos = c.toString (). indexOf ('(');
        калі (argsPos! == -1) {
            вярнуць c.toString (). падрадку (9, argsPos);
        }
        вярнуць нуль;
    }
}

Нарэшце, давайце паглядзім на выкарыстанне. Прыкладам з'яўляецца тое ж самае тэставанне блока, што і раней (яно («ачышчае папярэдні вынік») з той нязначнай розніцай, што мы будзем выкарыстоўваць планавальнік тэсту замест fakeAsync / галачкі.

хай testScheduler;
beforeEach (() => {
    testScheduler = новы SpyingTestScheduler ();
    testScheduler.maxFrames = 1000000;
    monkeypatchScheduler (testScheduler);
    fixture.detectChanges ();
});
beforeEach (() => {
    spyOn (apiService, 'запыт'). and.callFake (() => {
        вярнуцца Observable.of (queryResult) .delay (REQUEST_DELAY);
    });
});
гэта ('ачышчае папярэдні вынік', (зроблена: функцыя) => {
    comp.options = ['не пуста'];
    testScheduler.onAction ((actionName: string, затрымка: лік, памылка ?: любы) => {
        калі (actionName === 'DebounceTimeSubscriber' && затрымка === DEBOUNCING_VALUE) {
            чакаць (comp.options.length) .toBe (0, `было [$ {comp.options.join (',')}]`);
            зроблена ();
        }
    });
    SpecUtils.focusAndInput ("Лонда", прыстасаванне, "уваход");
    fixture.detectChanges ();
    testScheduler.flush ();
});

Планіроўшчык выпрабаванняў ствараецца і адпраўляецца (!) У першую чаргу перад кожным. У другім перадEach мы шпіёнім за apiService.query, каб падаваць вынік queryResult пасля REQUEST_DELAY = 5000 мілісекунд.

Давайце праходзім яго па радку:

  1. Перш за ўсё, звярніце ўвагу, што мы аб'яўляем выкананую функцыю, якая нам неабходная ў спалучэнні з функцыяй зваротнага званка выкліку планавальніка. Гэта азначае, што нам трэба сказаць Жасмін, што тэст робіцца самастойна.
гэта ('ачышчае папярэдні вынік', (зроблена: функцыя) => {

2. Зноў робім выгляд, што ў кампаненце ёсць некаторыя варыянты.

comp.options = ['не пуста'];

3. Гэта патрабуе некаторага тлумачэння, паколькі з першага погляду здаецца, што ён нязграбны. Мы хочам чакаць дзеяння пад назвай "DebounceTimeSubscriber" са спазненнем DEBOUNCING_VALUE = 300 мілісекунд. Калі гэта адбудзецца, мы хочам праверыць, ці мае параметр.length 0. Затым тэст завершаны, і мы называем done ().

testScheduler.onAction ((actionName: string, затрымка: лік, памылка ?: любы) => {
    калі (actionName === 'DebounceTimeSubscriber' && затрымка === DEBOUNCING_VALUE) {
      чакаць (comp.options.length) .toBe (0, `было [$ {comp.options.join (',')}]`);
      зроблена ();
    }
});

Вы бачыце, што выкарыстанне планавальнікаў тэстаў патрабуе некаторых спецыяльных ведаў аб унутранай рэалізацыі Rxjs. Зразумела, гэта залежыць ад таго, якім планавальнікам тэсту вы карыстаецеся, але нават калі вы самастойна ўкараніце магутны планіроўшчык, вам трэба будзе разумець планавальнікі і падвяргаць некаторым значэнням выканання для гнуткасці (што, зноў жа, не можа тлумачыцца самастойна).

4. Зноў жа, карыстальнік ўводзіць значэнне "Londo".

SpecUtils.focusAndInput ("Лонда", прыстасаванне, "уваход");

5. Зноў выявіце змены і паўторна выкладзіце шаблон.

fixture.detectChanges ();

6. Нарэшце, мы выконваем усе дзеянні, размешчаныя ў чарзе планавання.

testScheduler.flush ();

Рэзюмэ

Уласныя ўтыліты для вуглавых утылітаў пераважней самаробных… да таго часу, пакуль яны працуюць. У некаторых выпадках пара fakeAsync / цікаў не працуе, але няма прычын адчайвацца і апускаць адзінкавыя тэсты. У гэтых выпадках дарога - аўтаматычная ўтыліта для сінхранізацыі (тут таксама вядомая як AsyncZoneTimeInSyncKeeper) альбо карыстацкі планавальнік тэстаў (тут таксама ведаюць як SpyingTestScheduler).

Зыходны код