זוויתית: בדיקת דברים אסינכרוניים באזור fakedAsync VS. מתן מתזמנים מותאמים אישית

נשאלתי פעמים רבות שאלות על "האזור המזויף" וכיצד להשתמש בו. לכן החלטתי לכתוב מאמר זה כדי לשתף את התצפיות שלי כשמדובר במבחני "fakeAsync" מדויקים.

האזור הוא חלק מכריע במערכת האקולוגית הזוויתית. אפשר היה לקרוא שהאזור עצמו הוא רק סוג של "הקשר ביצוע". למעשה, קוף זוויתי תופס את הפונקציות הגלובליות כמו setTimeout או setInterval על מנת ליירט פונקציות שמבוצעות לאחר עיכוב כלשהו (setTimeout) או מעת לעת (setInterval).

חשוב להזכיר שמאמר זה לא הולך להראות כיצד להתמודד עם פריצות setTimeout. מכיוון ש- Angular עושה שימוש כבד ב- RxJs מה שמסתמך על פונקציות תזמון מקוריות (יתכן שתופתעו אבל זה נכון), היא משתמשת באזור ככלי מורכב ועם זאת חזק בכדי להקליט את כל הפעולות האסינכרוניות שעשויות להשפיע על מצב היישום. Angular מיירט אותם על מנת לדעת האם יש עדיין עבודה בתור. זה מנקז את התור בהתאם לשעה. ככל הנראה, המשימות הניקוזות משנות את ערכי משתני הרכיב. כתוצאה מכך, התבנית שוב מוצגת.

עכשיו, כל הדברים שאינם קשורים באסינכרון הם לא מה שאנחנו צריכים לדאוג להם. פשוט נחמד להבין מה קורה מתחת למכסה המנוע כי זה עוזר לכתוב בדיקות יחידה אפקטיביות. יתרה מזאת, לפיתוח מונחי מבחן השפעה עצומה על קוד המקור ("מקורו של TDD היה רצון להשיג בדיקות רגרסיה אוטומטיות חזקות שתמכו בעיצוב אבולוציוני. לאורך הדרך גילו המתרגלים שמבחני הכתיבה עשו תחילה שיפור משמעותי בתהליך העיצוב. "מרטין פאולר, https://martinfowler.com/articles/mocksArentStubs.html, 09/2017).

כתוצאה מכל המאמצים הללו אנו יכולים לשנות את הזמן כפי שאנו צריכים לבדוק את המצב בנקודת זמן מסוימת.

מתאר fakeAsync / tick

מסמכי ה- Angular קובעים כי ה- fakeAsync (https://angular.io/guide/testing#fake-async) מעניק חווית קידוד ליניארית יותר מכיוון שהוא נפטר מהבטחות כמו .whenStable (). ואז (...).

הקוד בתוך חסימת fakeAsync נראה כך:

קרצייה (100); // המתן למשימה הראשונה שתסתיים
fixture.detectChanges (); // תצוגת עדכון עם הצעת מחיר
תקתק (); // המתן למשימה השנייה שתסתיים
fixture.detectChanges (); // תצוגת עדכון עם הצעת מחיר

הקטעים הבאים נותנים תובנות לגבי אופן פעולתם של fakeAsync.

משתמשים כאן setTimeout / setInterval מכיוון שהם מראים בבירור מתי הפונקציות מבוצעות באזור fakeAsync. אפשר לצפות שפונקציית "זה" צריכה לדעת מתי הבדיקה נעשית (ביסמין המסודרת לפי טיעון שנעשה: פונקציה) אבל הפעם אנו מסתמכים על בן הזוג fakeAsync ולא על שימוש בכל סוג של התקשרות:

זה ('מנקז את משימת האזור לפי משימה', fakeAsync (() => {
        setTimeout (() => {
            תן לי = 0;
            const handle = setInterval (() => {
                אם (i ++ === 5) {
                    clearInterval (ידית);
                }
            }, 1000);
        }, 10000);
}));

זה מתלונן בקול כי עדיין יש כמה "טיימרים" (= setTimeouts) בתור:

שגיאה: טיימר (ים) 1 שנמצא עדיין בתור.

ברור מאליו שאנחנו צריכים לשנות את הזמן כדי לבצע את הפונקציה המוקדמת. אנו מצרפים את "הסימון" הפרמטר עם 10 שניות:

קרצייה (10000);

יו? השגיאה מבלבלת יותר. כעת, הבדיקה נכשלה בגלל ה"טיימרים התקופתיים "(" setIntervals ") המיועדים:

שגיאה: 1 טיימר תקופתי שנמצא עדיין בתור.

מכיוון שגילינו פונקציה את מה שיש לבצע בכל שנייה, אנו צריכים גם לשנות את הזמן על ידי שימוש בתיקייה שוב. הפונקציה מסיימת את עצמה לאחר 5 שניות. לכן אנו צריכים להוסיף עוד 5 שניות:

קרצייה (15000);

כעת, המבחן עובר. כדאי לומר שהאזור מזהה משימות הפועלות במקביל. פשוט הרחב את פונקציית הזמן הקצוב לביצוע באמצעות שיחת setInterval אחרת.

זה ('מנקז את משימת האזור לפי משימה', fakeAsync (() => {
    setTimeout (() => {
        תן לי = 0;
        const handle = setInterval (() => {
            אם (++ i === 5) {
                clearInterval (ידית);
            }
        }, 1000);
        תן ל j = 0;
        const handle2 = setInterval (() => {
            אם (++ j === 3) {
                clearInterval (ידית 2);
            }
        }, 1000);
    }, 10000);
    קרצייה (15000);
}));

המבחן עדיין עובר מכיוון ששני הקבוצות הללו נקבעו באותו הרגע. שניהם נעשים כאשר עוברות 15 שניות:

fakeAsync / סמן בפעולה

עכשיו אנו יודעים איך הדברים של fakeAsync / tick עובדים. תן לזה להשתמש בכמה דברים משמעותיים.

בואו נפתח תחום דמוי הצעה העומד בדרישות אלה:

  • זה תופס את התוצאה מכמה API (שירות)
  • זה מצמצם את קלט המשתמש על מנת לחכות למונח החיפוש הסופי (זה מקטין את מספר הבקשות); DEBOUNCING_VALUE = 300
  • זה מראה את התוצאה בממשק המשתמש ופולט את ההודעה המתאימה
  • מבחן היחידה מכבד את אופיו האסינכרוני של הקוד ובודק את ההתנהגות התקינה של השדה הדומה להציע מבחינת הזמן שחלף

בסופו של דבר, תרחישי בדיקה אלה:

לתאר ('בחיפוש', () => {
    זה ('מנקה את התוצאה הקודמת', fakeAsync (() => {
    }));
    זה ('פולט את אות ההתחלה', fakeAsync (() => {
    }));
    זה ('מצמצם את הלהיטים האפשריים של ה- API לבקשה אחת לכל DEBOUNCING_VALUE אלפיות השנייה', fakeAsync (() => {
    }));
});
לתאר ('על הצלחה', () => {
    זה ('קורא ל- API של google', fakeAsync (() => {
    }));
    זה ('פולט את אות ההצלחה עם מספר התאמות', fakeAsync (() => {
    }));
    זה ('מציג את הכותרות בשדה ההצעות', fakeAsync (() => {
    }));
});
לתאר ('בטעות', () => {
    זה ('פולט את אות השגיאה', fakeAsync (() => {
    }));
});

ב"חיפוש "איננו מחכים לתוצאת החיפוש. כאשר המשתמש מספק קלט (למשל "Lon") יש למחוק את האפשרויות הקודמות. אנו מצפים שהאופציות יהיו ריקות. בנוסף, יש לקלל את קלט המשתמש, נניח בערך של 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 ();
    verwag (comp.options.length) .toBe (0, `היה [$ {comp.options.join (',')}]`);
}));

קוד הרכיב המנסה לעמוד במבחן:

ngOnInit () {
    this.control.valueChanges.debounceTime (300). הירשם כמנוי (value => {
        this.options = [];
        this.suggest (ערך);
    });
}
מציעים (q: string) {
    this.googleBooksAPI.query (q). הירשם כמנוי (result => {
// ...
    }, () => {
// ...
    });
}

נעבור על הקוד צעד אחר צעד:

אנו מרגלים אחר שיטת שאילתת apiService אותה אנו הולכים לקרוא לרכיב. השאילתא משתנהResult מכילה כמה נתונים מדומים כגון "המלט", "מקבת" ו- "קינג ליר". בהתחלה אנו מצפים שהאופציות יהיו ריקות אך כפי שאולי שמתם לב שכל התור המזויף Synchronic מתנקז מתקתק (DEBOUNCING_VALUE) ולכן הרכיב מכיל את התוצאה הסופית של כתבי שייקספיר גם כן:

ציפה ש -3 יהיו 0, 'היה [המלט, מקבת, קינג ליר]'.

אנו זקוקים לעיכוב בבקשת שאילתת השירות על מנת לחקות מעבר אסינכרוני של זמן שנצרך על ידי שיחת ה- API. נוסיף עיכוב של 5 שניות (REQUEST_DELAY = 5000) וסמן (5000).

beforeEach (() => {
    spyOn (apiService, 'שאילתה'). and.returnValue (Observable.of (queryResult). Delay (1000));
});

fit ('מנקה את התוצאה הקודמת', fakeAsync (() => {
    comp.options = ['לא ריק'];
    SpecUtils.focusAndInput ('Lon', מתקן, 'קלט');
    סמן (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    verwag (comp.options.length) .toBe (0, `היה [$ {comp.options.join (',')}]`);
    סמן (REQUEST_DELAY);
}));

לדעתי, דוגמא זו צריכה לעבוד אך Zone.js טוען שעדיין יש עבודה בתור:

שגיאה: 1 טיימר תקופתי שנמצא עדיין בתור.

בשלב זה עלינו להעמיק לראות את הפונקציות בהן אנו חושדים שנתקעים באזור. קביעת מספר נקודות הפסקה היא הדרך ללכת:

ניקוי באגים באזור

ואז הנפק זאת בשורת הפקודה

_fakeAsyncTestZoneSpec._scheduler._ schedulerQueue [0] .args [0] [0]

או לבחון את תוכן האזור כך:

המממ, שיטת הסומק של AsyncScheduler עדיין נמצאת בתור ... למה?

שם הפונקציה שנחלקת הוא שיטת הסומק של AsyncScheduler.

סומק פומבי (פעולה: AsyncAction ): בטל {
  const {פעולות} = זה;
  אם (זה. פעיל) {
    פעולות. לדחוף (פעולה);
    להחזיר;
  }
  תן לטעות: כל;
  זה. פעיל = נכון;
  עשה {
    אם (שגיאה = action.execute (action.state, action.delay)) {
      לשבור;
    }
  } while (פעולה = פעולות. Shift ()); // למצות את תור המתזמן
  זה. פעיל = שקר;
  אם (שגיאה) {
    while (פעולה = פעולות. Shift ()) {
      action.subscribe ();
    }
    שגיאת לזרוק;
  }
}

כעת, אולי תוהה מה לא בסדר עם קוד המקור או האזור עצמו.

הבעיה היא שהאזור והקרציות שלנו אינן מסונכרנות.

לאזור עצמו יש את השעה הנוכחית (2017) ואילו התג רוצה לעבד את הפעולה המתוזמנת ב- 01.01.1970 + 300 מיליס + 5 שניות.

הערך של מתזמן האסינכרון מאשר כי:

ייבא {async as AsyncScheduler} מ- 'rxjs / scheduler / async';
// מקם את זה אי שם בתוך ה"זה "
console.info (AsyncScheduler.now ());
// → 1503235213879

AsyncZoneTimeInSync שומר להצלה

אחד התיקונים האפשריים לכך הוא להשתמש בכלי לשמור-בסנכרון כזה:

ייצוא מחלקה AsyncZoneTimeInSyncKeeper {
    זמן = 0;
    בנאי () {
        spyOn (AsyncScheduler, 'עכשיו'). and.callFake (() => {
            / * tslint: בטל את השורה הבאה * /
            console.info ('זמן', זמן זה);
            חזור זה. זמן;
        });
    }
    תקתק (זמן ?: מספר) {
        if (typeof time! == 'undefined') {
            this.time + = זמן;
            תקתק (this.time);
        } אחרת {
            תקתק ();
        }
    }
}

הוא עוקב אחר השעה הנוכחית שמוחזרת כעת () בכל פעם שמתקשר למתזמן האסינכרוני. זה עובד מכיוון שתפקוד הסימון () משתמש באותו זמן נוכחי. שניהם, המתזמן והאזור, חולקים את אותו הזמן.

אני ממליץ להעביר את ה- TimeInSyncKeeper לשלב לפני הכל:

לתאר ('בחיפוש', () => {
    תן timeInSyncKeeper;
    beforeEach (() => {
        timeInSyncKeeper = חדש AsyncZoneTimeInSyncKeeper ();
    });
});

כעת, נסתכל על השימוש בשומר הסנכרון בזמן. זכור שעלינו להתמודד עם סוגיית העיתוי הזו מכיוון ששדה הטקסט מופץ והבקשה אורכת זמן.

לתאר ('בחיפוש', () => {
    תן timeInSyncKeeper;
    beforeEach (() => {
        timeInSyncKeeper = חדש AsyncZoneTimeInSyncKeeper ();
        spyOn (apiService, 'שאילתה'). and.returnValue (ניתן לצפייה. של (queryResult). לעכב (REQUEST_DELAY));
    });
    זה ('מנקה את התוצאה הקודמת', fakeAsync (() => {
        comp.options = ['לא ריק'];
        SpecUtils.focusAndInput ('Lon', מתקן, 'קלט');
        timeInSyncKeeper.tick (DEBOUNCING_VALUE);
        fixture.detectChanges ();
        verwag (comp.options.length) .toBe (0, `היה [$ {comp.options.join (',')}]`);
        timeInSyncKeeper.tick (REQUEST_DELAY);
    }));
    // ...
});

נעבור על דוגמה זו שורה אחר שורה:

  1. הפעל את המופע של שומר הסנכרון
timeInSyncKeeper = חדש AsyncZoneTimeInSyncKeeper ();

2. בואו להגיב לשיטת apiService.query עם שאילתת תוצאה לאחר שעבר REQUEST_DELAY. נניח ששיטת השאילתה איטית ומגיבה לאחר REQUEST_DELAY = 5000 אלפיות השנייה.

spyOn (apiService, 'שאילתה'). and.returnValue (ניתן לצפייה. של (queryResult). לעכב (REQUEST_DELAY));

3. העמיד פנים שיש אפשרות 'לא ריק' בשדה ההצעה

comp.options = ['לא ריק'];

4. עבור לשדה "קלט" באלמנט הילידי של המתקן והוסף את הערך "Lon". זה מדמה את האינטראקציה של המשתמש עם שדה הקלט.

SpecUtils.focusAndInput ('Lon', מתקן, 'קלט');

5. בואו לעבור את פרק הזמן DEBOUNCING_VALUE באזור אסינכרון מזויף (DEBOUNCING_VALUE = 300 אלפיות שנייה).

timeInSyncKeeper.tick (DEBOUNCING_VALUE);

6. גלה שינויים וביצע מחדש את תבנית ה- HTML.

fixture.detectChanges ();

7. מערך האפשרויות ריק עכשיו!

verwag (comp.options.length) .toBe (0, `היה [$ {comp.options.join (',')}]`);

משמעות הדבר היא שהערכים הנצפים ששינוי המשמש ברכיבים הצליחו לפעול בזמן הנכון. שים לב כי הפונקציה debounceTime-d שבוצעה

ערך => {
    this.options = [];
    this.onEvent.emit ({signal: SuggestSignal.start});
    this.suggest (ערך);
}

דחף משימה נוספת לתור על ידי קריאה לשיטת ההצעה:

מציעים (q: string) {
    אם (! ש) {
        להחזיר;
    }
    this.googleBooksAPI.query (q). הירשם כמנוי (result => {
        אם (תוצאה) {
            this.options = result.items.map (item => item.volumeInfo);
            this.onEvent.emit ({signal: SuggestSignal.success, totalItems: result.totalItems});
        } אחרת {
            this.onEvent.emit ({signal: SuggestSignal.success, totalItems: 0});
        }
    }, () => {
        this.onEvent.emit ({signal: SuggestSignal.error});
    });
}

רק זכרו את המרגל בשיטת שאילתת ה- API של ספרי גוגל, שמגיב לאחר 5 שניות.

8. לבסוף, עלינו לתקתק שוב REQUEST_DELAY = 5000 אלפיות השנייה כדי לשטוף את תור האזור. הצפייה עליה אנו מנויים בשיטת ההצעה זקוקה ל- REQUEST_DELAY = 5000 כדי להשלים.

timeInSyncKeeper.tick (REQUEST_DELAY);

fakeAsync…? למה? יש מתזמנים!

המומחים של ReactiveX עשויים לטעון שנוכל להשתמש במתזמני בדיקות כדי להעמיד את הניתנים לבחינה. זה אפשרי ליישומי Angular אך יש לו כמה חסרונות:

  • זה דורש מכם להכיר את המבנה הפנימי של חפצים, מפעילים,…
  • מה אם יש לך כמה דרכים לעקיפת הבעיה ב- setTimeout ביישום? הם לא מטופלים על ידי המתזמנים.
  • החשוב ביותר: אני בטוח שאתה לא רוצה להשתמש במתזמנים בכל היישום שלך. אינך רוצה לערבב את קוד הייצור עם מבחני היחידות שלך. אתה לא רוצה לעשות דבר כזה:
const testScheduler;
אם (environment.test) {
    testScheduler = חדש YourTestScheduler ();
}
תן לצפייה;
if (testScheduler) {
    observable = observable.of ('ערך'). עיכוב (1000, testScheduler)
} אחרת {
    observable = ניתן לצפייה. של ('ערך'). עיכוב (1000);
}

זה לא פיתרון בר-קיימא. לדעתי, הפיתרון היחיד האפשרי הוא "להזריק" את מתזמן הבדיקה על ידי מתן סוג של "פרוקסי" לשיטות Rxjs האמיתיות. דבר נוסף שיש לקחת בחשבון הוא ששיטות עוקפות עלולות להשפיע לרעה על מבחני היחידה שנותרו. זו הסיבה שאנחנו הולכים להשתמש במרגלים של יסמין. מרגלים מתפנים אחרי כל זה.

הפונקציה monkeypatchScheduler עוטפת את יישום ה- Rxjs המקורי באמצעות מרגל. המרגל לוקח את טיעוני השיטה ומוסיף את TestScheduler במידת הצורך.

ייבא את {IScheduler} מ- 'rxjs / Scheduler';
יבוא {נצפה} מ- 'rxjs / Observable';
הצהיר var spyOn: פונקציה;
פונקציית ייצוא monkeypatchScheduler (מתזמן: IScheduler) {
    בואו ל observableMethods = ['concat', 'לדחות', 'ריק', 'forkJoin', 'אם', 'מרווח', 'להתמזג', 'של', 'טווח', 'לזרוק',
        'רוכסן'];
    תן operatorMethods = ['חוצץ', 'concat', 'עיכוב', 'נבדל', 'לעשות', 'כל', 'אחרון', 'להתמזג', 'מקס', 'לקחת',
        'timeInterval', 'lift', 'debounceTime'];
    תן injectFn = פונקציה (בסיס: כל, שיטות: מחרוזת []) {
        method.forEach (method => {
            const orig = base [שיטה];
            if (typeof orig === 'פונקציה') {
                spyOn (בסיס, שיטה) .and.callFake (פונקציה () {
                    תן args = Array.prototype.slice.call (טיעונים);
                    if (args [args.length - 1] && typeof args [args.length - 1] .now === 'פונקציה') {
                        args [args.length - 1] = מתזמן;
                    } אחרת {
                        args.push (מתזמן);
                    }
                    להחזיר orig.apply (זה, טוען);
                });
            }
        });
    };
    injectFn (Observable, observableMethods);
    injectFn (Observable.prototype, operatorMethods);
}

מעכשיו, testScheduler יבצע את כל העבודות בתוך Rxjs. זה לא משתמש setTimeout / setInterval או כל סוג של דברים אסינכרוניים. אין עוד צורך ב- fakeAsync.

עכשיו, אנו זקוקים למופע של מתזמן מבחן שברצוננו לעבור ל- monkeypatchScheduler.

זה מתנהג ממש כמו ברירת המחדל TestScheduler אך הוא מספק שיטת התקשרות חוזרת onAction. בדרך זו אנו יודעים איזו פעולה בוצעה לאחר פרק זמן זה.

כיתת ייצוא SpyingTestScheduler מרחיבה את VirtualTimeScheduler {
    spyFn: (פעולה שם: מחרוזת, עיכוב: מספר, שגיאה?: כל) => בטל;
    בנאי () {
        סופר (VirtualAction, defaultMaxFrame);
    }
    onAction (spyFn: (פעולה שם: מחרוזת, עיכוב: מספר, שגיאה ?: כל) => בטל) {
        this.spyFn = spyFn;
    }
    סומק() {
        const {פעולות, maxFrames} = זה;
        תן לשגיאה: כל, פעולה: AsyncAction ;
        בעוד ((action = actions.shift ()) && (this.frame = action.delay) <= maxFrames) {
            תן stateName = this.detectStateName (פעולה);
            תן לעכב = פעולה. לעכב;
            אם (שגיאה = action.execute (action.state, action.delay)) {
                אם (this.spyFn) {
                    this.spyFn (מצב שם, עיכוב, שגיאה);
                }
                לשבור;
            } אחרת {
                אם (this.spyFn) {
                    this.spyFn (מצב שם, עיכוב);
                }
            }
        }
        אם (שגיאה) {
            while (פעולה = פעולות. Shift ()) {
                action.subscribe ();
            }
            שגיאת לזרוק;
        }
    }
    private detectStateName (פעולה: AsyncAction ): מחרוזת {
        const c = Object.getPrototypeOf (action.state). קונסטרוקטור;
        const argsPos = c.toString (). indexOf ('(');
        אם (argsPos! == -1) {
            להחזיר c.toString (). מחרוזת (9, argsPos);
        }
        להחזיר null;
    }
}

לבסוף, נסתכל על השימוש. הדוגמא היא מבחן יחידה שנעשה בו שימוש בעבר (היא ('מנקה את התוצאה הקודמת') עם ההבדל הקל שאנו הולכים להשתמש במתזמן הבדיקה במקום ב- fakeAsync / tick.

תן testScheduler;
beforeEach (() => {
    testScheduler = חדש SpyingTestScheduler ();
    testScheduler.maxFrames = 1000000;
    monkeypatchScheduler (testScheduler);
    fixture.detectChanges ();
});
beforeEach (() => {
    spyOn (apiService, 'שאילתה'). and.callFake (() => {
        להחזיר Observable.of (queryResult). לעכב (REQUEST_DELAY);
    });
});
זה ('מנקה את התוצאה הקודמת', (done: Function) => {
    comp.options = ['לא ריק'];
    testScheduler.onAction ((פעולה שם: מחרוזת, עיכוב: מספר, שגיאה ?: יש) => {
        אם (actionName === 'DebounceTimeSubscribber' && עיכוב === DEBOUNCING_VALUE) {
            verwag (comp.options.length) .toBe (0, `היה [$ {comp.options.join (',')}]`);
            בוצע();
        }
    });
    SpecUtils.focusAndInput ('Londo', מתקן, 'קלט');
    fixture.detectChanges ();
    testScheduler.flush ();
});

מתזמן הבדיקה נוצר ונבחר בקוף (!) בראשון לפני כל אחד. בשניה שלפני כל אחד, אנו מרגלים אחר apiService.query כדי להגיש את שאילתת התוצאה לאחר REQUEST_DELAY = 5000 אלפיות השנייה.

עכשיו, נעבור על זה שורה אחר שורה:

  1. ראשית, שימו לב שאנחנו מכריזים על פונקציה שנעשתה אנו צריכים בשילוב עם פעולת ההתקשרות של מתזמן הבדיקה. המשמעות היא שאנו צריכים לומר לג'סמין כי הבדיקה נעשית בכוחות עצמנו.
זה ('מנקה את התוצאה הקודמת', (done: Function) => {

2. שוב, אנו מעמידים פנים כאילו כמה אפשרויות קיימות ברכיב.

comp.options = ['לא ריק'];

3. זה דורש הסבר כלשהו מכיוון שהוא נראה מעט מגושם ממבט ראשון. אנו רוצים לחכות לפעולה שנקראת "DebounceTimeSubscribber" בעיכוב של DEBOUNCING_VALUE = 300 אלפיות שנייה. כאשר זה קורה, אנו רוצים לבדוק אם האפשרות options.length היא 0. לאחר מכן, הבדיקה הושלמה ואנחנו קוראים לביצוע ().

testScheduler.onAction ((פעולה שם: מחרוזת, עיכוב: מספר, שגיאה ?: יש) => {
    אם (actionName === 'DebounceTimeSubscribber' && עיכוב === DEBOUNCING_VALUE) {
      verwag (comp.options.length) .toBe (0, `היה [$ {comp.options.join (',')}]`);
      בוצע();
    }
});

אתה רואה שהשימוש במתזמיני הבדיקה דורש ידע מיוחד על פנימי היישום של Rxjs. כמובן שזה תלוי באיזו מתזמן הבדיקות בו אתה משתמש, אך גם אם אתה מיישם מתזמן רב עוצמה בכוחות עצמך תצטרך להבין מתזמנים ולחשוף כמה ערכי זמן ריצה לגמישות (אשר, שוב, אולי לא יסביר את עצמם).

4. שוב, המשתמש מזין את הערך "Londo".

SpecUtils.focusAndInput ('Londo', מתקן, 'קלט');

5. שוב, גלה שינויים וביצע מחדש את התבנית.

fixture.detectChanges ();

6. לבסוף, אנו מבצעים את כל הפעולות המוצעות בתור של המתזמן.

testScheduler.flush ();

סיכום

כלי העזר לבדיקה של Angular עדיפים על פני התוצרת העצמית ... כל עוד הם עובדים. בחלק מהמקרים הזוג fake- Sync / tick אינו עובד אך אין סיבה לייאש ולהשמיט את בדיקות היחידה. במקרים אלה כלי סנכרון אוטומטי (המכונה גם בשם AsyncZoneTimeInSyncKeeper) או מתזמן בדיקות מותאם אישית (כאן ידוע גם בשם SpyingTestScheduler) הוא הדרך ללכת.

קוד מקור