Bucaqlı: fakilAsync zonasında async maddələrini sınamaq VS. xüsusi planlayıcıları təmin etmək

Mənə “saxta zona” və ondan necə istifadə ediləcəyi ilə bağlı dəfələrlə suallar verilib. Buna görə bu məqaləni incə "fakeAsync" testlərinə gəldikdə müşahidələrimi bölüşmək üçün yazmaq qərarına gəldim.

Zona Angular ekosisteminin vacib hissəsidir. Biri oxuya bilərdi ki, zonanın özü yalnız bir növ “icra etmə kontekstidir”. Əslində, bucaqlı monkey müəyyən gecikmədən (setTimeout) və ya dövri (setInterval) sonra icra olunan funksiyaları tutmaq üçün setTimeout və ya setInterval kimi qlobal funksiyaları həyata keçirir.

Qeyd etmək vacibdir ki, bu məqalə setTimeout hacks ilə necə davranacağını göstərməyəcək. Angular yerli vaxt funksiyalarına güvənən RxJ-lərdən ağır istifadə etdiyinə görə (təəccüblənə bilərsiniz, amma bu həqiqətdir), tətbiq vəziyyətinə təsir göstərə biləcək bütün asinxron hərəkətləri qeyd etmək üçün kompleksdən güclü bir vasitə kimi istifadə edir. Növbədə hələ bir işin olub olmadığını bilmək üçün bucaq onları tutur. Vaxtından asılı olaraq növbəni qurudur. Çox güman ki, qurudulmuş vəzifələr komponent dəyişənlərinin dəyərlərini dəyişdirir. Nəticədə şablon yenidən düzəldilir.

İndi bütün async məhsulları narahat olmağımız lazım deyil. Kaputun altında nələrin baş verdiyini başa düşmək sadəcə xoşdur, çünki bu, effektiv vahid testlərini yazmağa kömək edir. Üstəlik, test üsulu ilə hazırlanmış inkişaf mənbə koduna böyük təsir göstərir ("TDD'nin mənşəyi təkamül dizaynını dəstəkləyən güclü avtomatik reqressiya testi əldə etmək istəyi idi. Təcrübəçilər yazı testlərinin əvvəlcə dizayn prosesini əhəmiyyətli dərəcədə yaxşılaşdırdıqlarını aşkar etdilər). “Martin Fowler, https://martinfowler.com/articles/mocksArentStubs.html, 09/2017).

Bütün bu səylər nəticəsində vaxtı müəyyən bir nöqtədə sınamalı olduğumuz üçün dəyişə bilərik.

fakeAsync / qeyd konturu

Bucaq sənədləri, fakeAsync (https://angular.io/guide/testing#fake-async) daha çox xətti kodlaşdırma təcrübəsi qazandırdığını, çünki .WhenStable () kimi vədlərdən qurtulduğunu bildirir.

FakeAsync blokunun içindəki kod bu kimi görünür:

gənə (100); // ilk tapşırığın yerinə yetirilməsini gözləyin
armatur.detectChanges (); // sitatla yeniləmə görünüşü
gənə (); // ikinci işin bitməsini gözləyin
armatur.detectChanges (); // sitatla yeniləmə görünüşü

Aşağıdakı parçalar, fakeAsync-in necə işlədiyini izah edir.

FakeAsync zonasında funksiyaların nə vaxt yerinə yetirildiyini aydın göstərdikləri üçün setTimeout / setInterval burada istifadə olunur. Bu "bu" funksiyanın testin nə vaxt bitdiyini bilməlisiniz (Jasmində arqumentlə qurulmuşdur: Funksiya), ancaq bu dəfə hər cür zəngdən istifadə etməkdənsə, fakeAsync yoldaşına etibar edirik:

it ('bölgə vəzifəsini tapşırıqla qurutur', fakeAsync (() => {
        setTimeout (() => {
            qoy i = 0;
            const qolu = setInterval (() => {
                əgər (i ++ === 5) {
                    aydınInterval (sap);
                }
            }, 1000);
        }, 10000);
}));

Səs-küydən şikayətlənir, çünki növbədə hələ də bəzi “taymerlər” (= setTimeouts) var:

Xəta: 1 taymer (lər) hələ də növbədədir.

Aydındır ki, gözləmə rejimini yerinə yetirmək üçün vaxt dəyişdirməliyik. Parametrləşdirilmiş "işarə" ni 10 saniyə ilə əlavə edirik:

gənə (10000);

Hugh? Səhv daha çaşdırıcı olur. İndi sınanmış "dövri sayğaclar" (= setIntervals) səbəbindən test uğursuz oldu:

Xəta: Hələ növbədə olan 1 dövri timer (lər).

Hər saniyədə yerinə yetirilməli olan bir funksiyanı qəbul etdiyimiz üçün yenidən gəndən istifadə edərək vaxtı dəyişməliyik. Funksiya 5 saniyədən sonra özünü dayandırır. Buna görə başqa 5 saniyə əlavə etməliyik:

gənə (15000);

İndi test keçir. Bölgə paralel olaraq görülən vəzifələri tanıyır. Yalnız başqa bir SetInterval çağırışı ilə vaxtı uzadılmış funksiyanı genişləndirin.

it ('bölgə vəzifəsini tapşırıqla qurutur', fakeAsync (() => {
    setTimeout (() => {
        qoy i = 0;
        const qolu = setInterval (() => {
            əgər (++ i === 5) {
                aydınInterval (sap);
            }
        }, 1000);
        qoy j = 0;
        const qol2 = setInterval (() => {
            əgər (++ j === 3) {
                aydınInterval (qol2);
            }
        }, 1000);
    }, 10000);
    gənə (15000);
}));

Test hələ də davam edir, çünki hər iki setIntervals eyni anda başlandı. Hər ikisi də 15 saniyə keçdikdə edilir:

fakeAsync / fəaliyyətdə qeyd edin

İndi fakeAsync / gənə işlərinin necə işlədiyini bilirik. Bəzi mənalı şeylər üçün istifadə edək.

Bu tələblərə cavab verən bir təklif sahəsini inkişaf etdirək:

  • nəticəni bəzi API (xidmət) üzərindən götürür
  • son axtarış müddətini gözləmək üçün istifadəçi girişini bağlayır (sorğu sayını azaldır); DEBOUNCING_VALUE = 300
  • UI-də nəticəni göstərir və müvafiq mesaj yayır
  • vahid testi kodun asinxron xüsusiyyətinə hörmət edir və təklif olunan sahənin keçdiyi müddət baxımından düzgün davranışını yoxlayır

Bu sınaq ssenariləri ilə sona çatırıq:

təsvir edin ('axtarışda', () => {
    bu ('əvvəlki nəticəni təmizləyir', fakeAsync (() => {
    }));
    o ('başlanğıc siqnalını yayır', fakeAsync (() => {
    }));
    bu ('API'nin mümkün hitlərini DEBOUNCING_VALUE millisecund başına 1 sorğuya salır', fakeAsync (() => {
    }));
});
təsvir edin ('müvəffəqiyyət haqqında', () => {
    bu ('google API çağırır', fakeAsync (() => {
    }));
    bu ('matçların sayı ilə müvəffəqiyyət siqnalı yayır'), fakeAsync (() => {
    }));
    bu ('təklif olunan sahədəki başlıqları göstərir'), fakeAsync (() => {
    }));
});
təsvir edin ('səhv haqqında', () => {
    bu ('səhv siqnalını yayır', fakeAsync (() => {
    }));
});

"Axtarışda" axtarış nəticəsini gözləmirik. İstifadəçi bir giriş təmin etdikdə (məsələn, "Lon") əvvəlki seçimlər təmizlənməlidir. Seçimlərin boş olacağını gözləyirik. Bundan əlavə, istifadəçi girişi qarışıq olmalıdır, deyək ki, 300 millisaniyə dəyərində olmalıdır. Zona baxımından, 300 milislik mikrotask növbəyə tökülür.

Diqqəti çəkmək üçün bəzi təfərrüatları buraxmıram:

  • test qurğusu bucaqlı sənədlərdə göründüyü kimi olduqca çoxdur
  • apiService misalı fixture.debugElement.injector vasitəsilə enjekte edilir (…)
  • SpecUtils giriş və diqqət kimi istifadəçi ilə əlaqəli hadisələri tetikler
əvvəlEach (() => {
    spyOn (apiService, 'query'). və.returnValue (Müşahidə edilə bilən.of (queryResult));
});
fit ('əvvəlki nəticəni təmizləyir', fakeAsync (() => {
    comp.options = ['boş deyil'];
    SpecUtils.focusAndInput ('Lon', armatur, 'giriş');
    gənə (DEBOUNCING_VALUE);
    armatur.detectChanges ();
    gözləmək (comp.options.length) .toBe (0, `[$ {comp.options.join (',')}]`) idi;
}));

Testi təmin etməyə çalışan komponent kodu:

ngOnInit () {
    bu.control.valueChanges.debounceTime (300) .subscribe (value => {
        this.options = [];
        this.suggest (value);
    });
}
təklif (q: string) {
    this.googleBooksAPI.query (q) .subsubs (nəticə => {
// ...
    }, () => {
// ...
    });
}

Kod addım-addım keçək:

Komponentə zəng edəcəyimiz apiService sorğu metoduna casusuq. Dəyişən sorğuResult "Hamlet", "Macbeth" və "King Lear" kimi istehza məlumatlarını ehtiva edir. Əvvəlcə seçimlərin boş olacağını gözləyirik, amma gördüyünüz kimi bütün saxtaAsync növbəsi (DEBOUNCING_VALUE) suyu ilə quruyur və buna görə də komponent Şekspirin yazılarının son nəticəsini özündə birləşdirir:

Gözlənilən 3-ü 0 '' idi [Hamlet, Macbeth, King Lear] '.

API çağırışı ilə sərf olunan vaxtın asinxron keçidini təqlid etmək üçün xidmət sorğusu sorğusuna gecikməyə ehtiyac duyuruq. 5 saniyə gecikmə (REQUEST_DELAY = 5000) və qeyd (5000) əlavə edək.

əvvəlEach (() => {
    spyOn (apiService, 'query'). və.returnValue (Müşahidə edilə bilən.of (queryResult) .delay (1000));
});

fit ('əvvəlki nəticəni təmizləyir', fakeAsync (() => {
    comp.options = ['boş deyil'];
    SpecUtils.focusAndInput ('Lon', armatur, 'giriş');
    gənə (DEBOUNCING_VALUE);
    armatur.detectChanges ();
    gözləmək (comp.options.length) .toBe (0, `[$ {comp.options.join (',')}]`) idi;
    gənə (REQUEST_DELAY);
}));

Məncə, bu nümunə işləməlidir, lakin Zone.js iddia edir ki, növbədə hələ də işlər var:

Xəta: Hələ növbədə olan 1 dövri timer (lər).

Bu nöqtədə zonada ilişib qalmaq üçün şübhə etdiyimiz funksiyaları görmək üçün daha dərinə getməliyik. Bəzi keçid nöqtələrini təyin etmək getmək üçün bir yoldur:

fakeAsync zonasını düzəltmək

Sonra, əmr satırına verin

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

və ya zonanın məzmununu bu kimi araşdırın:

hmmm, AsyncScheduler-in flush metodu hələ növbədədir ... niyə?

Düşünülən funksiyanın adı AsyncScheduler’s flush metodudur.

ictimai flush (fəaliyyət: AsyncAction ): boşluq {
  const {tədbirlər} = bu;
  əgər (this.active) {
    hərəkətlər.push (fəaliyyət);
    qayıtmaq;
  }
  xəta edək: hər hansı;
  this.active = doğrudur;
  etmək {
    əgər (səhv = action.execute (action.state, action.delay)) {
      fasilə;
    }
  } while (action = action.shift ()); // planlayıcı növbəsini tükəndirmək
  this.active = saxta;
  əgər (səhv) {
    while (action = action.shift ()) {
      action.unsubscribe ();
    }
    atma səhv;
  }
}

İndi mənbə kodu və ya zonanın özü ilə nəyin səhv olduğunu düşünə bilərsiniz.

Problem budur ki, zona və gənələrimiz sinxron deyil.

Zonanın özündə cari vaxt var (2017), gənə 01.01.1970 + 300 millis + 5 saniyədə planlaşdırılan hərəkəti emal etmək istəyir.

Async planlayıcısının dəyəri bunu təsdiqləyir:

'rxjs / scheduler / async' dən {async AsyncScheduler} idxal edin;
// buranı "bu" içərisinə yerləşdirin
konsol.info (AsyncScheduler.now ());
// → 1503235213879

AsyncZoneTimeInSyncKeeper qurtarmağa

Bunun üçün mümkün düzəlişlərdən biri bu kimi sinxronizasiya sinxronizasiya proqramının olmasıdır:

AsyncZoneTimeInSyncKeeper ixrac sinfi
    vaxt = 0;
    konstruktor () {
        spyOn (AsyncScheduler, 'indi'). və .callFake (() => {
            / * tslint: əlil-sonrakı xətt * /
            konsol.info ('zaman', this.time);
            qayıt bu.time;
        });
    }
    işarə (vaxt?: sayı) {
        əgər (tipof vaxt! == 'müəyyən edilməmiş') {
            bu.time + = vaxt;
            gənə (this.time);
        } başqa {
            gənə ();
        }
    }
}

Async planlayıcısı çağırıldıqda, indi () tərəfindən geri qaytarılan cari vaxtı izləyir. Bu işarədir () funksiyası eyni cari vaxtdan istifadə etdiyinə görə. Həm planlayıcı, həm də bölgə eyni vaxtda paylaşır.

Əvvəlki mərhələdə vaxtı təyin etməyi məsləhət görürəm:

təsvir edin ('axtarışda', () => {
    vaxt verinSyncKeeper;
    əvvəlEach (() => {
        timeInSyncKeeper = yeni AsyncZoneTimeInSyncKeeper ();
    });
});

İndi vaxt senkronizatorunun istifadəsinə nəzər salaq. Unutmayın ki, bu vaxt məsələsini həll etməliyik, çünki mətn sahəsi açıldı və sorğu bir müddət çəkir.

təsvir edin ('axtarışda', () => {
    vaxt verinSyncKeeper;
    əvvəlEach (() => {
        timeInSyncKeeper = yeni AsyncZoneTimeInSyncKeeper ();
        spyOn (apiService, 'sorğu'). və.returnValue (Müşahidə edilə bilən.of (queryResult) .delay (REQUEST_DELAY));
    });
    bu ('əvvəlki nəticəni təmizləyir', fakeAsync (() => {
        comp.options = ['boş deyil'];
        SpecUtils.focusAndInput ('Lon', armatur, 'giriş');
        timeInSyncKeeper.tick (DEBOUNCING_VALUE);
        armatur.detectChanges ();
        gözləmək (comp.options.length) .toBe (0, `[$ {comp.options.join (',')}]`) idi;
        timeInSyncKeeper.tick (REQUEST_DELAY);
    }));
    // ...
});

Bu nümunə xətti xətti ilə keçək:

  1. sinxron saxlayıcı nümunəsini tezləşdirin
timeInSyncKeeper = yeni AsyncZoneTimeInSyncKeeper ();

2. REQUEST_DELAY keçdikdən sonra apiService.query metoduna nəticə sorğusu ilə cavab verin. Sorgulama metodunun yavaş olduğunu və REQUEST_DELAY = 5000 millisekunddan sonra cavab verdiyini söyləyək.

spyOn (apiService, 'sorğu'). və.returnValue (Müşahidə edilə bilən.of (queryResult) .delay (REQUEST_DELAY));

3. Təklif olunan sahədə "boş olmayan" bir seçim var

comp.options = ['boş deyil'];

4. Armaturun doğma elementindəki "giriş" sahəsinə gedin və "Lon" dəyərini daxil edin. Bu istifadəçi giriş sahəsi ilə qarşılıqlı əlaqəsini simüle edir.

SpecUtils.focusAndInput ('Lon', armatur, 'giriş');

5. saxta async zonasında DEBOUNCING_VALUE müddətini keçməyə icazə verin (DEBOUNCING_VALUE = 300 millisekund).

timeInSyncKeeper.tick (DEBOUNCING_VALUE);

6. Dəyişiklikləri aşkar edin və HTML şablonunu yenidən göstərin.

armatur.detectChanges ();

7. Seçimlər silsiləsi boşdur!

gözləmək (comp.options.length) .toBe (0, `[$ {comp.options.join (',')}]`) idi;

Bu o deməkdir ki, komponentlərdə istifadə olunan dəyər dəyişikliyi vaxtında işə salınıb. Qeyd edək ki, icra olunan debounceTime-d funksiyası

dəyər => {
    this.options = [];
    this.onEvent.emit ({siqnal: SuggestSignal.start});
    this.suggest (value);
}

Metod təklif edərək başqa bir işi növbəyə itələdi:

təklif (q: string) {
    əgər (! q) {
        qayıtmaq;
    }
    this.googleBooksAPI.query (q) .subsubs (nəticə => {
        əgər (nəticə) {
            this.options = result.items.map (item => item.volumeInfo);
            this.onEvent.emit ({siqnal: SuggestSignal.success, totalItems: result.totalItems});
        } başqa {
            this.onEvent.emit ({siqnal: SuggestSignal.success, totalItems: 0});
        }
    }, () => {
        this.onEvent.emit ({siqnal: SuggestSignal.error});
    });
}

5 saniyədən sonra cavab verən google kitabları API sorğu metodunun casusunu xatırlayın.

8. Nəhayət, zona növbəsini qızartmaq üçün yenidən REQUEST_DELAY = 5000 millisaniyədə işarələməliyik. Təklif olunan metodda abunə olduğumuz müşahidə olunmaq üçün REQUEST_DELAY = 5000 lazımdır.

timeInSyncKeeper.tick (REQUEST_DELAY);

fakeAsync…? Niyə? Planlayıcılar var!

ReactiveX mütəxəssisləri, müşahidə olunanları test edilə bilmək üçün test planlayıcılarından istifadə edə biləcəyimizi iddia edə bilər. Bucaqlı tətbiqetmələr üçün mümkündür, lakin bir sıra çatışmazlıqları var:

  • müşahidə aparanların daxili quruluşu ilə tanış olmağı tələb edir, operatorlar,…
  • tətbiqinizdə bəzi çirkin setTimeout işləmələri varsa nə etməlisiniz? Onlar planlaşdıranlar tərəfindən idarə olunmur.
  • ən vacib biri: bütün tətbiqlərinizdə planlayıcılardan istifadə etmək istəmədiyinizə əmin deyiləm. İstehsal kodunu vahid testlərinizlə qarışdırmaq istəmirsiniz. Bu kimi bir şey etmək istəmirsiniz:
const testScheduler;
əgər (environment.test) {
    testScheduler = yeni YourTestScheduler ();
}
müşahidə olunsun;
əgər (testScheduler) {
    müşahidə edilə bilən = Müşahidə edilə bilən.of ('dəyər'). gecikmə (1000, testScheduler)
} başqa {
    müşahidə edilə bilən = Müşahidə edilə bilən.of ('dəyər'). gecikmə (1000);
}

Bu yararlı bir həll deyil. Məncə, mümkün olan yeganə həll test planlayıcısına həqiqi Rxjs metodları üçün "etibarlılar" təqdim etməklə "inyeksiya" etməkdir. Nəzərə alınmalı başqa bir şey, yalnış üsulların qalan vahid testlərinə mənfi təsir göstərə bilməsidir. Buna görə Jasmine'in casuslarından istifadə edəcəyik. Hər dəfə casuslar təmizlənir.

MonkeypatchScheduler funksiyası bir casus istifadə edərək orijinal Rxjs tətbiqini tamamlayır. Casus metodun dəlillərini götürür və lazım olduqda testScheduler-i əlavə edir.

'rxjs / Scheduler' dən {IScheduler} idxal edin;
'rxjs / Müşahidə edilə bilən' dən {Müşahidə edilə bilən} idxalı;
elan var spyOn: Funksiya;
ixrac funksiyası monkeypatchScheduler (planlayıcı: IScheduler) {
    icazə verilirMethods = ['uyğunlaşdırmaq', 'təxirə salmaq', 'boş', 'forkJoin', 'əgər', 'interval', 'birləşmək', 'of', 'diapazon', 'atmaq',
        'zip'];
    operatorMethods = ['bufer', 'uyğunlaşdırmaq', 'gecikdirmək', 'fərqli', 'etmək', 'hər', 'son', 'birləşmək', 'max', 'götürmək',
        'timeInterval', 'qaldırmaq', 'debounceTime'];
    injectFn = funksiyaya icazə verin (baza: hər hansı, üsullar: string []) {
        üsullar.forEach (metod => {
            const orig = əsas [metod];
            əgər (typeof orig === 'funksiya') {
                spyOn (əsas, metod) .and.callFake (funksiya () {
                    let args = Array.prototype.slice.call (dəlillər);
                    əgər (args [args.length - 1] && typeof args [args.length - 1] .now === 'function') {
                        args [args.length - 1] = planlayıcı;
                    } başqa {
                        args.push (planlayıcı);
                    }
                    qayıtmaq orig.apply (bu, args);
                });
            }
        });
    };
    injectFn (Müşahidə edilə bilən, Müşahidəli Metodlar);
    injectFn (Müşahidə edilə bilən.prototype, operatorMetodlar);
}

Bundan sonra testScheduler Rxjs daxilində bütün işləri icra edəcəkdir. Bu SetTimeout / setInterval və ya hər hansı bir async məhsulları istifadə etmir. Artıq fakeAsync üçün ehtiyac yoxdur.

İndi monkeypatchScheduler'ə keçmək istədiyimiz bir test planlayıcısı nümunəsinə ehtiyacımız var.

Varsayılan TestScheduler kimi çox davranır, ancaq onAction-a zəng vurma metodu verir. Bu yolla, hansı müddətdən sonra hansı hərəkətin icra edildiyini bilirik.

ixrac sinif SpyingTestScheduler VirtualTimeScheduler-i genişləndirir
    spyFn: (actionName: string, gecikmə: nömrə, səhv?: hər hansı) => boş;
    konstruktor () {
        super (VirtualAction, defaultMaxFrame);
    }
    onAction (spyFn: (actionName: string, gecikmə: nömrə, səhv?: hər hansı bir) => etibarsız) {
        this.spyFn = spyFn;
    }
    qızarmaq() {
        const {hərəkətlər, maxFrames} = bu;
        xəta edək: hər hansı bir, fəaliyyət: AsyncAction ;
        while ((action = action.shift ()) && (this.frame = action.delay) <= maxFrames) {
            let stateName = this.detectStateName (fəaliyyət);
            gecikməyə imkan verin = action.delay;
            əgər (səhv = action.execute (action.state, action.delay)) {
                əgər (this.spyFn) {
                    this.spyFn (stateName, gecikmə, səhv);
                }
                fasilə;
            } başqa {
                əgər (this.spyFn) {
                    this.spyFn (dövlət Adı, gecikmə);
                }
            }
        }
        əgər (səhv) {
            while (action = action.shift ()) {
                action.unsubscribe ();
            }
            atma səhv;
        }
    }
    şəxsi aşkarStateName (fəaliyyət: AsyncAction ): string {
        const c = Object.getPrototypeOf (action.state) .konstruktor;
        const argsPos = c.toString (). indexOf ('(');
        əgər (argsPos! == -1) {
            geri c.toString (). alt sətir (9, argsPos);
        }
        geri qayıtmaq;
    }
}

Nəhayət, istifadəyə bir nəzər salaq. Nümunə, əvvəllər istifadə edilən eyni (vahid (əvvəlki nəticəni təmizləyir) testi ilə fakeAsync / qeyd əvəzinə test planlayıcısından istifadə edəcəyimiz kiçik fərqlə).

qoy testScheduler;
əvvəlEach (() => {
    testScheduler = yeni SpyingTestScheduler ();
    testScheduler.maxFrames = 1000000;
    monkeypatchScheduler (testScheduler);
    armatur.detectChanges ();
});
əvvəlEach (() => {
    spyOn (apiService, 'query'). və .callFake (() => {
        Observable.of (sorğuResult) qayıtmaq. geri göndərmək (REQUEST_DELAY);
    });
});
it ('əvvəlki nəticəni təmizləyir', (görülən: Funksiya) => {
    comp.options = ['boş deyil'];
    testScheduler.onAction ((actionName: string, gecikmə: nömrə, səhv?: hər hansı) => {
        əgər (actionName === 'DebounceTimeSubscriber' && gecikmə === DEBOUNCING_VALUE) {
            gözləmək (comp.options.length) .toBe (0, `[$ {comp.options.join (',')}]`) idi;
            görülən ();
        }
    });
    SpecUtils.focusAndInput ('Londo', armatur, 'giriş');
    armatur.detectChanges ();
    testScheduler.flush ();
});

Test planlayıcısı ilk əvvəldən əvvəl yaradılmışdır və monkeypatched (!). Əvvəlki ikinci hissədə, REQUEST_DELAY = 5000 millisekunddan sonra nəticə sorğusuna xidmət etmək üçün apiService.query-də casusluq edirik.

İndi bu xətti xətti ilə keçək:

  1. Əvvəlcə qeyd edək ki, test planlayıcısının geri çağırış onAction ilə birlikdə ehtiyac duyduğumuz funksiyanı elan edirik. Bu, Jasminə imtahanın özümüzün edildiyini söyləməyimiz deməkdir.
it ('əvvəlki nəticəni təmizləyir', (görülən: Funksiya) => {

2. Yenə də komponentdə mövcud olan bəzi variantları iddia edirik.

comp.options = ['boş deyil'];

3. Bunun üçün bəzi izahat tələb olunur, çünki ilk baxışdan bir az çirkin görünür. DEBOUNCING_VALUE = 300 millisekund gecikmə ilə "DebounceTimeSubscriber" adlı bir hərəkət gözləmək istəyirik. Bu baş verdikdə, seçimlərin.length-un 0 olub olmadığını yoxlamaq istəyirik. Sonra test başa çatdı və çağırırıq ().

testScheduler.onAction ((actionName: string, gecikmə: nömrə, səhv?: hər hansı) => {
    əgər (actionName === 'DebounceTimeSubscriber' && gecikmə === DEBOUNCING_VALUE) {
      gözləmək (comp.options.length) .toBe (0, `[$ {comp.options.join (',')}]`) idi;
      görülən ();
    }
});

Görürsən ki, test planlayıcılarının istifadəsi Rxjs-in həyata keçirilməsi barədə bəzi xüsusi biliklər tələb edir. Əlbəttə ki, hansı test planlayıcısından istifadə etməyinizə bağlıdır, lakin özünüzdə güclü bir planlayıcı tətbiq etsəniz də, planlayıcıları başa düşməli və rahatlıq üçün bəzi iş saatları dəyərini açıqlamalı olacaqsınız (yenə öz-özünə izah edilə bilməz).

4. Yenə də istifadəçi "Londo" dəyərini daxil edir.

SpecUtils.focusAndInput ('Londo', armatur, 'giriş');

5. Yenə dəyişiklikləri aşkar edin və şablonu yenidən göstərin.

armatur.detectChanges ();

6. Sonda, planlaşdırıcının növbəsində yerləşdirilmiş bütün işləri yerinə yetiririk.

testScheduler.flush ();

Xülasə

Angular'ın öz sınaq proqramları, özləri işləyənlərə üstünlük verilir ... işlədikləri müddətdə. Bəzi hallarda fakeAsync / gənə cütü işləmir, ancaq vahid testlərini çıxartmaq üçün heç bir səbəb yoxdur. Bu hallarda avtomatik sinxronizasiya proqramı (burada AsyncZoneTimeInSyncKeeper kimi də tanınır) və ya xüsusi bir test planlayıcısı (burada SpyingTestScheduler olaraq da bilin) ​​getmək üçün bir yoldur.

Mənbə kodu