Këndor: Testimi i sendeve të asinkut në zonën e falsifikuarAsync VS. sigurimi i orareve me porosi

Më kanë bërë shumë herë pyetje në lidhje me "zonën e rreme" dhe si ta përdorin atë. Kjo është arsyeja pse vendosa ta shkruaj këtë artikull për të ndarë vëzhgimet e mia kur bëhet fjalë për testet "fakeAsync" të grira.

Zona është një pjesë thelbësore e ekosistemit Angular. Dikush mund të ketë lexuar se zona në vetvete është vetëm një lloj "konteksti përmbarimi". Në fakt, Angular Monkeypatches funksionet globale të tilla si setTimeout ose setInterval në mënyrë që të përgjoj funksionet që ekzekutohen pas disa vonesave (setTimeout) ose në mënyrë periodike (setInterval).

Shtë e rëndësishme të përmendet që ky artikull nuk do të tregojë se si të merreni me hacks e setTimeout. Meqenëse Angular përdor përdorimin e rëndë të RxJs ajo që mbështetet në funksionet e kohës së duhur (mund të befasoheni por është e vërtetë), ai përdor zonën si një mjet kompleks por të fuqishëm për të regjistruar të gjitha veprimet asinkron që mund të ndikojnë në gjendjen e aplikacionit. Angular i përgjon ato për të ditur nëse ka akoma punë në radhë. Kullon radhë në varësi të kohës. Me shumë mundësi, detyrat e drenazhuara ndryshojnë vlerat e ndryshoreve të përbërësit. Si rezultat, shablloni bëhet përsëri.

Tani, të gjitha sendet e asinkut nuk janë ato për të cilat duhet të shqetësohemi. Shtë thjesht bukur të kuptosh se çfarë ndodh nën kapuç sepse ndihmon për të shkruar teste njësie efektive. Për më tepër, zhvillimi i drejtuar nga testi ka një ndikim të madh në kodin burimor ("Origjina e TDD ishte një dëshirë për të marrë një test të fortë regresioni automatik që mbështeste projektimin evolucionar. Gjatë rrugës që zbuluan praktikuesit e saj që testet e shkrimit së pari bënë një përmirësim të rëndësishëm në procesin e projektimit. "Martin Fowler, https://martinfowler.com/articles/mocksArentStubs.html, 09/2017).

Si rezultat i të gjitha këtyre përpjekjeve mund të zhvendosim kohën pasi duhet të testojmë për gjendjen në një moment specifik kohor.

skicë fakeAsync / shënoni

Dokumentet Angular shprehen se fakeAsync (https://angular.io/guide/testing#fake-async) mundëson një përvojë më lineare të kodimit, sepse shpëton nga premtimet siç janë .whenStable () atëherë (...).

Kodi brenda bllokut fakeAsync duket si ky:

tick (100); // prisni që detyra e parë të bëhet
fixture.detectChanges (); // pamje të azhurnuar me kuotë
tick (); // prisni që detyra e dytë të bëhet
fixture.detectChanges (); // pamje të azhurnuar me kuotë

Copat e mëposhtme japin disa pasqyrë në mënyrën sesi funksionon fakeAsync.

setTimeout / setInterval po përdoren këtu sepse ato tregojnë qartë kur funksionet ekzekutohen në zonën fakeAsync. Ju mund të prisni që ky funksion "ajo" duhet të dijë se kur bëhet testi (në Jasmine të rregulluar me argumentin e bërë: Funksioni), por kësaj radhe ne mbështetemi te shoqëruesi fakeAsync në vend se të përdorim çfarëdo lloj reagimi:

ajo ('kullon detyrën e zonës me detyrë', fakeAsync (() =>
        setTimeout (() =>
            le i = 0;
            const handle = setInterval (() =>
                nëse (i ++ === 5)
                    clearInterval (trajtuar);
                }
            }, 1000);
        }, 10000);
}));

Ajo ankohet me zë të lartë sepse ka ende disa "kohëmatës" (= setTimeout) në radhë:

Gabim: 1 kohëmatës (të) akoma në radhë.

Shtë e qartë se duhet të zhvendosim kohën për të realizuar funksionin e ndërprerë me kohë. Ne bashkojmë me "sekuencën" e parameterizuar me 10 sekonda:

tick (10000);

Hugh? Gabimi bëhet më konfuz. Tani, testi dështon për shkak të "kohëmatësve periodikë" të mbyllur (= setIntervals):

Gabim: 1 kohëmatës (et) periodik akoma në radhë.

Meqenëse kemi pasur një funksion që duhet të ekzekutohet çdo sekondë, duhet të zhvendosim kohën duke përdorur përsëri shënimin. Funksioni përfundon vetë pas 5 sekondash. Kjo është arsyeja pse ne duhet të shtojmë edhe 5 sekonda:

tick (15000);

Tani, testi po kalon. Vlen të thuhet se zona njeh detyrat që zhvillohen paralelisht. Thjesht zgjasni funksionin e ndërprerë nga një telefonatë tjetër e vendosur.

ajo ('kullon detyrën e zonës me detyrë', fakeAsync (() =>
    setTimeout (() =>
        le i = 0;
        const handle = setInterval (() =>
            nëse (++ i === 5)
                clearInterval (trajtuar);
            }
        }, 1000);
        le j = 0;
        const handle2 = setInterval (() =>
            nëse (++ j === 3)
                clearInterval (handle2);
            }
        }, 1000);
    }, 10000);
    tick (15000);
}));

Testi po vazhdon ende, sepse të dy ata grupe të interevaleve janë filluar në të njëjtin moment. Të dy bëhen kur të kalojnë 15 sekonda:

fakeAsync / shënoni në veprim

Tani ne e dimë se si po funksionon fakeAsync / shënoni. Lëreni të përdorë për disa gjëra kuptimplote.

Le të zhvillojmë një fushë të sugjeruar, e cila i plotëson këto kërkesa:

  • ajo kap rezultatin nga disa API (shërbim)
  • ajo hedh inputin e përdoruesit për të pritur termin përfundimtar të kërkimit (zvogëlon numrin e kërkesave); DEBOUNCING_VALUE = 300
  • ai tregon rezultatin në UI dhe lëshon mesazhin e duhur
  • testi i njësisë respekton natyrën asinkrike të kodit dhe teston sjelljen e duhur të fushës së sugjeruar në drejtim të kohës së kaluar

Ne përfundojmë me këtë skenarë testimi:

përshkruaj ('në kërkim', () => {
    ajo ('heq rezultatin e mëparshëm', fakeAsync (() => {
    }));
    ajo ('lëshon sinjalin e fillimit', fakeAsync (() =>
    }));
    ("po godet goditjet e mundshme të API-së në 1 kërkesë për milje sekonda DEBOUNCING_VALUE", fakeAsync (() =>
    }));
});
përshkruani ('mbi suksesin', () => {
    ajo ('quan API google', fakeAsync (() =>
    }));
    ajo ('lëshon sinjalin e suksesit me numrin e ndeshjeve', fakeAsync (() =>
    }));
    ajo ('tregon titujt në fushën sugjeruese'), fakeAsync (() =>
    }));
});
përshkruaj ('në gabim', () =>
    ajo ('lëshon sinjalin e gabimit', fakeAsync (() =>
    }));
});

Në "në kërkim" ne nuk presim rezultatin e kërkimit. Kur përdoruesi siguron një hyrje (për shembull "Lon"), opsionet e mëparshme duhet të pastrohen. Ne presim që opsionet të jenë boshe. Për më tepër, inputi i përdoruesit duhet të hidhet, le të themi me një vlerë prej 300 milisekonda. Për sa i përket zonës, një mikrotaskë prej 300 miljesh është shtyrë në radhë.

Vini re, që unë heq disa detaje për shkurtësinë:

  • konfigurimi i provës është pothuajse i njëjtë siç shihet në dokumentet Angular
  • instanca apiService injektohet përmes ndeshjeve.debugElement.injector (…)
  • SpecUtils nxit ngjarjet në lidhje me përdoruesit si input dhe përqëndrim
paraEach (() =>
    spyOn (apiService, 'pyetje'). dhe.returnValue (Observable.of (pyetjaResult));
});
fit ("pastron rezultatin e mëparshëm", fakeAsync (() =>
    comp.options = ['jo bosh'];
    SpecUtils.focusAndInput ('Lon', ndeshjeve, 'input');
    tick (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    pres (comp.options.l gjatësi) .toBe (0, `ishte [$ {comp.options.join (',')}]`);
}));

Kodi i komponentit që përpiqet të kënaqë provën:

ngOnInit ()
    kjo.control.valueChanges.debounceTime (300) .kërkohu (vlera =>
        kjo.options = [];
        this.suggest (vlera);
    });
}
sugjeroni (q: varg) {
    kjo.googleBooksAPI.query (q) .shkruaj (rezultat =>
// ...
    }, () =>
// ...
    });
}

Le ta kalojmë kodin hap pas hapi:

Ne spiunojmë metodën e pyetjes apiService të cilën do ta quajmë në përbërës. Pyetja e ndryshueshmeResult përmban disa të dhëna tallesh si "Hamlet", "Macbeth" dhe "King Lear". Në fillim ne presim që opsionet të jenë boshe por siç mund ta keni vërejtur i gjithë radha e fakeAsync derdhet me rriqër (DEBOUNCING_VALUE) dhe për këtë arsye përbërësi përmban rezultatin përfundimtar të shkrimeve të Shekspirit gjithashtu:

Pritej që 3 të ishin 0, ‘ishte [Hamleti, Macbeth, Mbreti Lear] '.

Ne kemi nevojë për një vonesë për kërkesën e pyetjes së shërbimit në mënyrë që të imitojmë një kalim asinkron të kohës së harxhuar nga thirrja API. Le të shtojmë 5 sekonda vonesë (REQUEST_DELAY = 5000) dhe shëno (5000).

paraEach (() =>
    spyOn (apiService, 'query'). dhe.returnValue (Observable.of (queryResult) .delay (1000));
});

fit ("pastron rezultatin e mëparshëm", fakeAsync (() =>
    comp.options = ['jo bosh'];
    SpecUtils.focusAndInput ('Lon', ndeshjeve, 'input');
    tick (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    pres (comp.options.l gjatësi) .toBe (0, `ishte [$ {comp.options.join (',')}]`);
    tick (REQUEST_DELAY);
}));

Sipas mendimit tim, ky shembull duhet të funksionojë, por Zone.js pretendon se ka ende disa punë në radhë:

Gabim: 1 kohëmatës (et) periodik akoma në radhë.

Në këtë pikë ne duhet të shkojmë më thellë për të parë ato funksione që ne dyshojmë të mbërthyer në zonë. Vendosja e disa pikave të ndërprerjes është mënyra për të vazhduar:

debugging zonë fakeAsync

Pastaj, lëshoni këtë në vijën e komandës

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

ose shqyrtoni përmbajtjen e zonës si kjo:

hmmm, metoda e skuqur e AsyncScheduler është ende në radhë ... pse?

Emri i funksionit që është duke u mbyllur është metoda e skuqjes e AsyncScheduler.

turret e publikut (veprim: AsyncAction ): pavlefshme {
  const {veprime} = kjo;
  nëse (kjo.aktive) {
    actions.push (veprimi);
    kthehen;
  }
  le gabim: ndonjë;
  kjo.aktive = e vërtetë;
  bej {
    nëse (gabimi = veprim.execute (veprim.statari, veprim.delay)) {
      thyer;
    }
  ndërsa (veprim = veprime.shift ()); // shter radhën e planit
  kjo.aktive = false;
  nëse (gabim) {
    ndërsa (veprim = veprime.shift ()) {
      action.unsubscribe ();
    }
    gabim hedh;
  }
}

Tani, mund të pyesni veten se çfarë është e gabuar me kodin burimor ose vetë zonën.

Problemi është se zona dhe shënimet tona janë jashtë sinkronizimit.

Zona në vetvete ka kohën e tanishme (2017) ndërsa rriqrat dëshirojnë të përpunojnë veprimet e planifikuara në 01.01.1970 + 300 milis + 5 sekonda.

Vlera e planifikuesit async konfirmon që:

importoni {async si AsyncScheduler} nga 'rxjs / scheduler / async';
// vendoseni këtë diku brenda "it"
console.info (AsyncScheduler.now ());
// → 1503235213879

AsyncZoneTimeInSyncKeeper në shpëtim

Një zgjidhje e mundshme për këtë është të keni një mjet të vazhdueshëm-sinkronizues si ky:

klasa e eksportit AsyncZoneTimeInSyncKeeper
    koha = 0;
    konstruktor ()
        spyOn (AsyncScheduler, 'tani'). dhe.callFake (() =>
            / * tslint: çaktivizo-rreshtin tjetër * /
            console.info ('koha', kjo.do kohë);
            ktheni këtë.time;
        });
    }
    shënoni (koha ?: numri) {
        nëse (koha e tipit! == 'e papërcaktuar') {
            kjo.kohë + = koha;
            tick (this.time);
        } tjeter
            tick ();
        }
    }
}

Mban gjurmët e kohës aktuale që kthehet nga tani () sa herë që quhet orari i asyncit. Kjo funksionon sepse funksioni i shënjave () përdor të njëjtën kohë aktuale. Të dy, programuesi dhe zona, ndajnë të njëjtën kohë.

Unë rekomandoj që të hapni kohënInSyncKeeper në fazën para secilit:

përshkruaj ('në kërkim', () => {
    le kohëInSyncKeeper;
    paraEach (() =>
        timeInSyncKeeper = reja AsyncZoneTimeInSyncKeeper ();
    });
});

Tani, le të shohim në përdorimin e rojtarit të sinkronizimit të kohës. Mbani në mend se duhet të trajtojmë këtë çështje kohore sepse fusha e tekstit është debuar dhe kërkesa kërkon pak kohë.

përshkruaj ("në kërkim", () => {
    le kohëInSyncKeeper;
    paraEach (() =>
        timeInSyncKeeper = reja AsyncZoneTimeInSyncKeeper ();
        spyOn (apiService, 'query'). dhe.returnValue (Observable.of (queryResult) .delay (REQUEST_DELAY));
    });
    ajo ('heq rezultatin e mëparshëm', fakeAsync (() => {
        comp.options = ['jo bosh'];
        SpecUtils.focusAndInput ('Lon', ndeshjeve, 'input');
        timeInSyncKeeper.tick (DEBOUNCING_VALUE);
        fixture.detectChanges ();
        pres (comp.options.l gjatësi) .toBe (0, `ishte [$ {comp.options.join (',')}]`);
        timeInSyncKeeper.tick (REQUEST_DELAY);
    }));
    // ...
});

Le ta kalojmë këtë shembull rresht me radhë:

  1. çastoni shembullin e mbajtësit të sinkronizimit
timeInSyncKeeper = reja AsyncZoneTimeInSyncKeeper ();

2. le të përgjigjet me metodën apiService.query me pyetjen e rezultatitResult pas REQUEST_DELAY ka kaluar. Le të themi se metoda e pyetjes është e ngadaltë dhe përgjigjet pas REQUEST_DELAY = 5000 milisekonda.

spyOn (apiService, 'query'). dhe.returnValue (Observable.of (queryResult) .delay (REQUEST_DELAY));

3. Pretendoni se ekziston një opsion ‚jo bosh‘ i pranishëm në fushën e sugjerimit

comp.options = ['jo bosh'];

4. Shkoni në fushën "hyrje" në elementin vendor të ndeshjes dhe vendosni vlerën "Lon". Kjo simulon ndërveprimin e përdoruesit me fushën e hyrjes.

SpecUtils.focusAndInput ('Lon', ndeshjeve, 'input');

5. le të kalojë periudhën kohore DEBOUNCING_VALUE në zonën e rreme të asinkut (DEBOUNCING_VALUE = 300 milisekonda).

timeInSyncKeeper.tick (DEBOUNCING_VALUE);

6. Zbuloni ndryshimet dhe ri-jepni modelin HTML.

fixture.detectChanges ();

7. Grupi i opsioneve është bosh tani!

pres (comp.options.l gjatësi) .toBe (0, `ishte [$ {comp.options.join (',')}]`);

Kjo do të thotë që vlerat e vëzhguaraCanzhimet e përdorura në përbërës u arrit të funksiononin në kohën e duhur. Vini re se funksioni i ekzekutuar i debitimitTime-d

vlera =>
    kjo.options = [];
    this.onEvent.emit ({sinjal: SuggestSignal.start);
    this.suggest (vlera);
}

shtyu një detyrë tjetër në radhë duke thirrur metodën të sugjerojë:

sugjeroni (q: varg) {
    nëse (! q)
        kthehen;
    }
    kjo.googleBooksAPI.query (q) .shkruaj (rezultat =>
        nëse (rezultati) {
            this.options = result.items.map (artikulli => artikull.volumeInfo);
            this.onEvent.emit ({sinjal: SuggestSignal.success, totalItems: result.totalItems});
        } tjeter
            this.onEvent.emit ({sinjal: SuggestSignal.success, totalItems: 0});
        }
    }, () =>
        this.onEvent.emit ({sinjal: SuggestSignal.error});
    });
}

Vetëm kujtoni spiunin në metodën e pyetjes API të librave google i cili përgjigjet pas 5 sekondash.

8. Më në fund, duhet të shënojmë përsëri për REQUEST_DELAY = 5000 milisekonda në mënyrë që të skuqemi radhë e zonës. Vëzhgimi për të cilin ne pajtohemi në metodën sugjeruese, ka nevojë për REQUEST_DELAY = 5000 për të përfunduar.

timeInSyncKeeper.tick (REQUEST_DELAY);

fakeAsync ...? Pse? Ka skedarë!

Ekspertët e ReactiveX mund të argumentojnë se ne mund të përdorim programuesit e provave për t'i bërë të vëzhgueshme mbikëqyrësit. Applicationsshtë e mundur për aplikacione Angular, por ka disa disavantazhe:

  • kërkon që të njiheni me strukturën e brendshme të vëzhguesve, operatorëve,…
  • po sikur të keni disa aplikime të shëmtuara për kohën e duhur në programin tuaj? Ata nuk trajtohen nga programuesit.
  • më e rëndësishmja: Jam i sigurt që nuk dëshiron të përdorësh programuesit në tërë aplikacionin tënd. Ju nuk dëshironi të përzieni kodin e prodhimit me testet e njësisë suaj. Ju nuk dëshironi të bëni diçka të tillë:
const testScheduler;
nëse (mjedisi.testi) {
    testScheduler = YourTestScheduler i ri ();
}
le të vëzhgueshme;
nëse (testiScheduler) {
    vëzhgues = Vëzhgueshëm.of (‘vlerë’). vonesë (1000, provëScheduler)
} tjeter
    i vëzhgueshëm = i vëzhgueshëm.of (‘vlerë’). vonesë (1000);
}

Kjo nuk është një zgjidhje e zbatueshme. Sipas mendimit tim, e vetmja zgjidhje e mundshme është të "injektoni" planifikuesin e provës duke siguruar një lloj "proxies" për metodat reale të Rxjs. Një tjetër gjë për t'u marrë parasysh është se metodat kryesore mund të ndikojnë negativisht në testet e njësive të mbetura. Kjo është arsyeja pse ne do të përdorim spiunët e Jasmine. Spiunët pastrohen pas çdo gjëje.

Funksioni monkeypatchScheduler përfundon zbatimin origjinal të Rxjs duke përdorur një spiun. Spiuni merr argumentet e metodës dhe shton testinScheduler nëse është e përshtatshme.

importi {IScheduler} nga 'rxjs / Scheduler';
importi {Vëzhgueshëm} nga 'rxjs / Observable';
deklaroni var spyOn: Funksioni;
funksioni i eksportit monkeypatchScheduler (skeduluesi: IScheduler) {
    le të vëzhgueshmeMethods = ['konkat', 'shtyr', 'bosh', 'forkJoin', 'nëse', 'interval', 'bashkim', 'i', 'varg', 'hedh',
        'Zip'];
    lejoni që operatori Metet = ['buffer', 'konkat', 'vonesë', 'i veçantë', 'bëj', 'çdo', 'i fundit', 'bashkojë', 'max', 'marr',
        'timeInterval', 'ashensor', 'debounceTime'];
    le injectFn = funksion (bazë: ndonjë, metodat: varg []) {
        metoda.për çdo (metodë =>
            const orig = bazë [metodë];
            nëse (tipi i origjinës === 'funksioni') {
                spyOn (bazë, metodë) .and.callFake (funksion () {
                    le args = Array.prototype.slice.call (argumente);
                    nëse (argumenton [argumentet. gjatësia - 1] && lloji i argumenteve [args.l gjatësia - 1]. njohja === 'funksioni') {
                        args [args.l gjatësi - 1] = planifikues;
                    } tjeter
                        args.push (scheduler);
                    }
                    kthimi origjinal.apply (kjo, argumenton);
                });
            }
        });
    };
    injectFn (Metodat e vëzhgueshme, të vëzhgueshme);
    injectFn (Vëzhgueshëm.prototipi, operatori Metodat);
}

Tani e tutje, testScheduler do të ekzekutojë të gjithë punën brenda Rxjs. Ai nuk përdor setTimeout / setInterval ose çfarëdo lloj sendesh async. Nuk ka më nevojë për fakeAsync.

Tani, kemi nevojë për një shembull të planifikuesit të testit që duam t'i kalojmë monkeypatchScheduler.

Ai sillet shumë si TestScheduler i paracaktuar, por siguron një metodë kthyese ndajAction. Në këtë mënyrë, ne e dimë se cili veprim u ekzekutua pas cilën periudhe kohore.

klasa e eksportit SpyingTestScheduler shtrihet VirtualTimeScheduler
    spyFn: (veprimName: varg, vonesë: numër, gabim ?: ndonjë) => pavlefshme;
    konstruktor ()
        super (VirtualAction, defaultMaxFrame);
    }
    onAction (spyFn: (veprimName: varg, vonesë: numër, gabim ?: ndonjë) => pavlefshme) {
        kjo.spyFn = spyFn;
    }
    turret ()
        veprimet const,, maxFrames} = kjo;
        le gabim: çdo, veprim: AsyncAction ;
        ndërsa ((veprim = veprime.shift ()) && (kjo.frame = veprim.delay) <= maxFrames)
            le stateName = kjo.detectStateName (veprim);
            le vonimi = veprim.delay;
            nëse (gabimi = veprim.execute (veprim.statari, veprim.delay)) {
                nëse (kjo.spyFn)
                    this.spyFn (Emri i shtetit, vonesa, gabimi);
                }
                thyer;
            } tjeter
                nëse (kjo.spyFn)
                    this.spyFn (Emri i shtetit, vonesa);
                }
            }
        }
        nëse (gabim) {
            ndërsa (veprim = veprime.shift ()) {
                action.unsubscribe ();
            }
            gabim hedh;
        }
    }
    detectStateName private (veprim: AsyncAction ): varg {
        const c = Object.getPrototypeOf (veprim.state) .konstruktor;
        const argsPos = c.toString (). indexOf ('('));
        nëse (argumentetPos! == -1)
            kthimi c.toString (). nënshartesa (9, argumentetPos);
        }
        kthimi nul;
    }
}

Më në fund, le t'i hedhim një vështrim përdorimit. Shembulli është i njëjti provë njësie siç është përdorur më parë (ai ("pastron rezultatin e mëparshëm") me ndryshimin e vogël që ne do të përdorim planifikuesin e testit në vend të fakeAsync / shënoni.

le testScheduler;
paraEach (() =>
    testScheduler = SpyingTestScheduler i ri ();
    testScheduler.maxFrames = 1000000;
    monkeypatchScheduler (testScheduler);
    fixture.detectChanges ();
});
paraEach (() =>
    spyOn (apiService, 'pyetje'). dhe.callFake (() =>
        kthimi Observable.of (pyetjaResult) .delay (REQUEST_DELAY);
    });
});
ajo ('pastron rezultatin e mëparshëm', (bëhet: Funksioni) =>
    comp.options = ['jo bosh'];
    testScheduler.onAction ((veprim Emri: varg, vonesë: numër, gabim ?: ndonjë) => {
        nëse (veprimName === 'DebounTimeSubscriber' && vonimi === DEBOUNCING_VALUE) {
            pres (comp.options.l gjatësi) .toBe (0, `ishte [$ {comp.options.join (',')}]`);
            bëhet ();
        }
    });
    SpecUtils.focusAndInput ('Londo', ndeshja, 'input');
    fixture.detectChanges ();
    testScheduler.flush ();
});

Orari i provës është krijuar dhe monkeypatched (!) Në të parën para se të gjitha. Në sekondën e dytë para secilit, ne spiunojmë në apiService.query në mënyrë që të shërbejmë pyetjen e rezultatitResult pas REQUEST_DELAY = 5000 milisekonda.

Tani, le të kalojmë përmes vijës vijore:

  1. Para së gjithash, vini re se ne deklarojmë funksionin e kryer për të cilin kemi nevojë së bashku me përgjigjen e programuesit të testit në Vizitë. Kjo do të thotë që ne duhet t'i tregojmë Jasmine se testi është bërë vetë.
ajo ('pastron rezultatin e mëparshëm', (bëhet: Funksioni) =>

2. Përsëri, ne pretendojmë disa opsione të pranishme në përbërës.

comp.options = ['jo bosh'];

3. Kjo kërkon disa shpjegime sepse duket se është pak e ngathët në pamje të parë. Ne duam të presim një veprim të quajtur "DebounTimeSubscriber" me një vonesë të DEBOUNCING_VALUE = 300 milisekonda. Kur kjo të ndodhë, ne duam të kontrollojmë nëse opsionet. gjatësia është 0. Pastaj, testi është përfunduar dhe ne e quajmë të bërë ().

testScheduler.onAction ((veprim Emri: vargu, vonesa: numri, gabimi ?: ndonjë) =>
    nëse (veprimName === 'DebounTimeSubscriber' && vonimi === DEBOUNCING_VALUE) {
      pres (comp.options.l gjatësi) .toBe (0, `ishte [$ {comp.options.join (',')}]`);
      bëhet ();
    }
});

Ju shikoni që përdorimi i orarit të testimit kërkon njohuri të veçanta në lidhje me internalet e zbatimit të Rxjs. Sigurisht që varet se çfarë skeduluesi i testit përdorni, por edhe nëse implementoni një programues të fuqishëm më vete, do t'ju duhet të kuptoni programuesit dhe të ekspozoni disa vlera të ekzekutimit për fleksibilitet (i cili, përsëri, mund të mos jetë i vetë-shpjegueshëm).

4. Përsëri, përdoruesi fut vlerën "Londo".

SpecUtils.focusAndInput ('Londo', ndeshja, 'input');

5. Përsëri, zbuloni ndryshimet dhe ri-jepni modelin.

fixture.detectChanges ();

6. Më në fund, ne ekzekutojmë të gjitha veprimet e vendosura në radhë të planifikuesit.

testScheduler.flush ();

përmbledhje

Shërbimet e testimit të vetë Angular janë më të preferueshme nga ato të bëra vetë ... për sa kohë që funksionojnë. Në disa raste çifti fakeAsync / shënoni nuk funksionon por nuk ka asnjë arsye për të dëshpëruar dhe hequr testet e njësisë. Në këto raste një mjet automatik për sinkronizimin (këtu njihet edhe si AsyncZoneTimeInSyncKeeper) ose një programues i provave me porosi (këtu njihni edhe si SpyingTestScheduler) është mënyra për të shkuar.

Kodi i burimit