Angular: Menguji hal-hal async dalam zona fakedAsync VS. menyediakan penjadwal khusus

Saya telah berkali-kali ditanya pertanyaan tentang "zona palsu" dan bagaimana menggunakannya. Itu sebabnya saya memutuskan untuk menulis artikel ini untuk membagikan pengamatan saya ketika datang ke tes "fakeAsync".

Zona ini merupakan bagian penting dari ekosistem Angular. Orang mungkin telah membaca bahwa zona itu sendiri hanya semacam "konteks eksekusi". Bahkan, Angular monkeypatches fungsi global seperti setTimeout atau setInterval untuk mencegat fungsi yang dieksekusi setelah beberapa penundaan (setTimeout) atau secara berkala (setInterval).

Penting untuk menyebutkan bahwa artikel ini tidak akan menunjukkan cara menangani peretasan setTimeout. Karena Angular banyak menggunakan RxJ yang bergantung pada fungsi waktu asli (Anda mungkin akan terkejut tapi itu benar), ia menggunakan zona sebagai alat yang kompleks namun kuat untuk merekam semua tindakan asinkron yang mungkin mempengaruhi keadaan aplikasi. Angular mencegat mereka untuk mengetahui apakah masih ada beberapa pekerjaan dalam antrian. Menguras antrian tergantung pada waktu. Kemungkinan besar, tugas yang dikeringkan mengubah nilai-nilai variabel komponen. Akibatnya, templat akan dirender ulang.

Sekarang, semua hal async bukan yang perlu kita khawatirkan. Hanya baik untuk memahami apa yang terjadi di bawah tenda karena itu membantu untuk menulis tes unit yang efektif. Selain itu, pengembangan yang digerakkan oleh tes memiliki dampak besar pada kode sumber ("Asal-usul TDD adalah keinginan untuk mendapatkan pengujian regresi otomatis yang kuat yang mendukung desain evolusioner. Di sepanjang jalan para praktisi menemukan bahwa tes menulis pertama kali membuat peningkatan yang signifikan pada proses desain. “Martin Fowler, https://martinfowler.com/articles/mocksArentStubs.html, 09/2017).

Sebagai hasil dari semua upaya ini kita dapat menggeser waktu karena kita perlu menguji keadaan pada titik waktu tertentu.

fakeAsync / centang garis besar

Dokumen Angular menyatakan bahwa fakeAsync (https://angular.io/guide/testing#fake-async) memberikan pengalaman pengkodean yang lebih linier karena menghilangkan janji seperti .whenStable (). Then (...).

Kode di dalam blok fakeAsync terlihat seperti ini:

centang (100); // tunggu tugas pertama selesai
fixture.detectChanges (); // perbarui tampilan dengan kutipan
kutu, mencentang, menandakan(); // tunggu tugas kedua selesai
fixture.detectChanges (); // perbarui tampilan dengan kutipan

Cuplikan berikut memberikan wawasan tentang cara kerja fakeAsync.

setTimeout / setInterval digunakan di sini karena mereka jelas menunjukkan kapan fungsi dieksekusi di zona fakeAsync. Anda mungkin berharap bahwa fungsi "it" harus tahu kapan tes dilakukan (dalam Jasmine diatur oleh argumen yang dilakukan: Function) tetapi kali ini kita bergantung pada pendamping Async palsu daripada menggunakan segala jenis panggilan balik:

it ('menguras zona tugas berdasarkan tugas', fakeAsync (() => {
        setTimeout (() => {
            biarkan saya = 0;
            const handle = setInterval (() => {
                if (i ++ === 5) {
                    clearInterval (handle);
                }
            }, 1000);
        }, 10000);
}));

Ia mengeluh dengan keras karena masih ada beberapa "timer" (= setTimeouts) dalam antrian:

Kesalahan: 1 timer masih dalam antrian.

Sudah jelas bahwa kita perlu menggeser waktu untuk menyelesaikan fungsi timeout. Kami menambahkan parameter "„ centang "dengan 10 detik:

centang (10000);

Hugh? Kesalahan semakin membingungkan. Sekarang, tes gagal karena enqueued "penghitung waktu periodik" (= setIntervals):

Kesalahan: 1 timer berkala masih dalam antrian.

Karena kita memerlukan fungsi yang harus dijalankan setiap detik, kita juga perlu menggeser waktu dengan menggunakan tanda centang lagi. Fungsi berakhir sendiri setelah 5 detik. Itu sebabnya kami perlu menambahkan 5 detik lagi:

centang (15000);

Sekarang, tes ini berlalu. Perlu dikatakan bahwa zona mengenali tugas yang berjalan secara paralel. Hanya perluas fungsi timeouted dengan panggilan setInterval lainnya.

it ('menguras zona tugas berdasarkan tugas', fakeAsync (() => {
    setTimeout (() => {
        biarkan saya = 0;
        const handle = setInterval (() => {
            if (++ i === 5) {
                clearInterval (handle);
            }
        }, 1000);
        biarkan j = 0;
        const handle2 = setInterval (() => {
            if (++ j === 3) {
                clearInterval (handle2);
            }
        }, 1000);
    }, 10000);
    centang (15000);
}));

Tes masih berlalu karena kedua setInterval telah dimulai pada saat yang sama. Keduanya dilakukan ketika 15 detik berlalu:

fakeAsync / centang dalam aksi

Sekarang kita tahu bagaimana barang-barang fakeAsync / tick bekerja. Biarkan itu digunakan untuk beberapa hal yang bermakna.

Mari kita mengembangkan bidang saran-seperti yang memenuhi persyaratan ini:

  • itu mengambil hasil dari beberapa API (layanan)
  • itu membatasi input pengguna untuk menunggu istilah pencarian akhir (ini mengurangi jumlah permintaan); DEBOUNCING_VALUE = 300
  • itu menunjukkan hasil di UI dan memancarkan pesan yang sesuai
  • tes unit menghormati sifat asinkron dari kode dan menguji perilaku yang tepat dari bidang saran-seperti dalam hal waktu berlalu

Kami berakhir dengan skenario pengujian ini:

jelaskan ('saat pencarian', () => {
    it ('hapus hasil sebelumnya', fakeAsync (() => {
    }));
    it ('memancarkan sinyal awal', fakeAsync (() => {
    }));
    itu ('membatasi kemungkinan hit API ke 1 permintaan per DEBOUNCING_VALUE milidetik', fakeAsync (() => {
    }));
});
jelaskan ('saat sukses', () => {
    it ('call the google API', fakeAsync (() => {
    }));
    it ('memancarkan sinyal sukses dengan jumlah kecocokan', fakeAsync (() => {
    }));
    it ('menunjukkan judul di bidang saran', fakeAsync (() => {
    }));
});
jelaskan ('saat salah', () => {
    it ('memancarkan sinyal kesalahan', fakeAsync (() => {
    }));
});

Dalam "saat pencarian" kami tidak menunggu hasil pencarian. Ketika pengguna memberikan input (misalnya "Lon"), opsi sebelumnya harus dihapus. Kami berharap opsi kosong. Selain itu, input pengguna harus dibatasi, katakanlah dengan nilai 300 milidetik. Dalam hal zona, 300 milis-mikrotask didorong ke antrian.

Perhatikan, bahwa saya menghilangkan beberapa detail untuk singkatnya:

  • pengaturan tes hampir sama dengan yang terlihat pada dokumen Angular
  • instance apiService disuntikkan melalui fixture.debugElement.injector (...)
  • SpecUtils memicu peristiwa terkait pengguna seperti input dan fokus
beforeEach (() => {
    spyOn (apiService, 'query'). and.returnValue (Observable.of (queryResult));
});
fit ('hapus hasil sebelumnya', fakeAsync (() => {
    comp.options = ['tidak kosong'];
    SpecUtils.focusAndInput ('Lon', fixture, 'input');
    centang (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    ekspektasi (comp.options.length) .toBe (0, `adalah [$ {comp.options.join (',')}]`);
}));

Kode komponen berusaha memuaskan tes:

ngOnInit () {
    this.control.valueChanges.debounceTime (300) .scribe (value => {
        this.options = [];
        ini.saran (nilai);
    });
}
sarankan (q: string) {
    this.googleBooksAPI.query (q) .subscribe (result => {
// ...
    }, () => {
// ...
    });
}

Mari kita melalui kode langkah demi langkah:

Kami memata-matai metode permintaan apiService yang akan kita panggil dalam komponen. Variabel queryResult berisi beberapa data tiruan seperti "Hamlet", "Macbeth" dan "King Lear". Pada awalnya kami mengharapkan opsi menjadi kosong tetapi karena Anda mungkin telah memperhatikan seluruh antrian fakeAsync dikeringkan dengan centang (DEBOUNCING_VALUE) dan karena itu komponen tersebut berisi hasil akhir dari tulisan Shakespeare juga:

Diharapkan 3 menjadi 0, ‘adalah [Hamlet, Macbeth, King Lear]’.

Kami membutuhkan penundaan permintaan permintaan layanan untuk meniru waktu yang tidak sinkron yang dikonsumsi oleh panggilan API. Mari kita tambahkan 5 detik keterlambatan (REQUEST_DELAY = 5000) dan centang (5000).

beforeEach (() => {
    spyOn (apiService, 'query'). and.returnValue (Observable.of (queryResult) .delay (1000));
});

fit ('hapus hasil sebelumnya', fakeAsync (() => {
    comp.options = ['tidak kosong'];
    SpecUtils.focusAndInput ('Lon', fixture, 'input');
    centang (DEBOUNCING_VALUE);
    fixture.detectChanges ();
    ekspektasi (comp.options.length) .toBe (0, `adalah [$ {comp.options.join (',')}]`);
    centang (REQUEST_DELAY);
}));

Menurut pendapat saya, contoh ini harus berfungsi tetapi Zone.js mengklaim bahwa masih ada beberapa pekerjaan dalam antrian:

Kesalahan: 1 timer berkala masih dalam antrian.

Pada titik ini kita harus masuk lebih dalam untuk melihat fungsi-fungsi yang kita curigai terjebak di zona tersebut. Menetapkan beberapa breakpoint adalah cara yang harus dilakukan:

debugging zona falseAsync

Kemudian, terbitkan ini di baris perintah

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

atau periksa konten zona seperti ini:

hmmm, metode flush AsyncScheduler masih dalam antrian ... mengapa?

Nama fungsi yang di-enqueued adalah metode flush AsyncScheduler.

public flush (tindakan: AsyncAction ): void {
  const {action} = ini;
  if (this.active) {
    actions.push (aksi);
    kembali;
  }
  let error: any;
  this.active = true;
  lakukan {
    if (error = action.execute (action.state, action.delay)) {
      istirahat;
    }
  } while (action = action.shift ()); // buang antrian scheduler
  this.active = false;
  if (error) {
    while (action = actions.shift ()) {
      action.unsubscribe ();
    }
    kesalahan lemparan;
  }
}

Sekarang, Anda mungkin bertanya-tanya apa yang salah dengan kode sumber atau zona itu sendiri.

Masalahnya adalah bahwa zona dan kutu kami tidak sinkron.

Zona itu sendiri memiliki waktu saat ini (2017) sedangkan kutu ingin memproses tindakan yang dijadwalkan pada 01.01.1970 + 300 milid + 5 detik.

Nilai penjadwal async mengkonfirmasi bahwa:

impor {async as AsyncScheduler} dari 'rxjs / scheduler / async';
// letakkan ini di suatu tempat di dalam "it"
console.info (AsyncScheduler.now ());
// → 1503235213879

AsyncZoneTimeInSyncKeeper untuk menyelamatkan

Salah satu kemungkinan perbaikan untuk ini adalah memiliki utilitas keep-in-sync seperti ini:

kelas ekspor AsyncZoneTimeInSyncKeeper {
    waktu = 0;
    constructor () {
        spyOn (AsyncScheduler, 'now'). and.callFake (() => {
            / * tslint: disable-next-line * /
            console.info ('waktu', kali ini);
            kembalikan this.time;
        });
    }
    centang (waktu ?: angka) {
        if (typeof time! == 'undefined') {
            this.time + = waktu;
            centang (kali ini);
        } lain {
            kutu, mencentang, menandakan();
        }
    }
}

Ini melacak waktu saat ini yang dikembalikan oleh now () setiap kali penjadwal async dipanggil. Ini berfungsi karena fungsi tick () menggunakan waktu saat ini yang sama. Baik, penjadwal dan zona, berbagi waktu yang sama.

Saya sarankan untuk instantiate timeInSyncKeeper pada fase beforeEach:

jelaskan ('saat pencarian', () => {
    biarkan timeInSyncKeeper;
    beforeEach (() => {
        timeInSyncKeeper = AsyncZoneTimeInSyncKeeper baru ();
    });
});

Sekarang, mari kita lihat penggunaan penjaga sinkronisasi waktu. Perlu diingat bahwa kami harus mengatasi masalah pengaturan waktu ini karena bidang teks ditolak dan permintaan membutuhkan waktu.

jelaskan (‘pada pencarian’, () => {
    biarkan timeInSyncKeeper;
    beforeEach (() => {
        timeInSyncKeeper = AsyncZoneTimeInSyncKeeper baru ();
        spyOn (apiService, 'query'). and.returnValue (Observable.of (queryResult) .delay (REQUEST_DELAY));
    });
    it ('hapus hasil sebelumnya', fakeAsync (() => {
        comp.options = ['tidak kosong'];
        SpecUtils.focusAndInput ('Lon', fixture, 'input');
        timeInSyncKeeper.tick (DEBOUNCING_VALUE);
        fixture.detectChanges ();
        ekspektasi (comp.options.length) .toBe (0, `adalah [$ {comp.options.join (',')}]`);
        timeInSyncKeeper.tick (REQUEST_DELAY);
    }));
    // ...
});

Mari kita telusuri contoh ini baris demi baris:

  1. instantiate instance dari penjaga sinkronisasi
timeInSyncKeeper = AsyncZoneTimeInSyncKeeper baru ();

2. biarkan merespons metode apiService.query dengan hasil queryResult setelah REQUEST_DELAY berlalu. Katakanlah metode kueri lambat dan merespons setelah REQUEST_DELAY = 5000 milidetik.

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

3. Berpura-pura ada opsi ‚tidak kosong‘ hadir di bidang saran

comp.options = ['tidak kosong'];

4. Pergi ke bidang "input" di elemen asli fixture dan masukkan nilai "Lon". Ini mensimulasikan interaksi pengguna dengan bidang input.

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

5. biarkan melewati periode waktu DEBOUNCING_VALUE dalam zona async palsu (DEBOUNCING_VALUE = 300 milidetik).

timeInSyncKeeper.tick (DEBOUNCING_VALUE);

6. Deteksi perubahan dan render ulang templat HTML.

fixture.detectChanges ();

7. Array opsi kosong sekarang!

ekspektasi (comp.options.length) .toBe (0, `adalah [$ {comp.options.join (',')}]`);

Ini berarti bahwa nilai yang dapat diamati. Perubahan yang digunakan dalam komponen dikelola untuk dijalankan pada waktu yang tepat. Perhatikan bahwa fungsi debounceTime-d yang dieksekusi

nilai => {
    this.options = [];
    this.onEvent.emit ({signal: SuggestSignal.start});
    ini.saran (nilai);
}

mendorong tugas lain ke dalam antrian dengan memanggil metode yang disarankan:

sarankan (q: string) {
    if (! q) {
        kembali;
    }
    this.googleBooksAPI.query (q) .subscribe (result => {
        jika (hasil) {
            this.options = result.items.map (item => item.volumeInfo);
            this.onEvent.emit ({signal: SuggestSignal.success, totalItems: result.totalItems});
        } lain {
            this.onEvent.emit ({signal: SuggestSignal.success, totalItems: 0});
        }
    }, () => {
        this.onEvent.emit ({signal: SuggestSignal.error});
    });
}

Ingat saja mata-mata pada metode kueri google books API yang merespons setelah 5 detik.

8. Akhirnya, kita harus mencentang lagi untuk REQUEST_DELAY = 5000 milidetik untuk menyiram antrian zona. Yang dapat diamati yang kami langgani dalam metode saran memerlukan REQUEST_DELAY = 5000 untuk menyelesaikan.

timeInSyncKeeper.tick (REQUEST_DELAY);

fakeAsync…? Mengapa? Ada penjadwal!

Para ahli ReactiveX mungkin berpendapat bahwa kita dapat menggunakan penjadwal uji untuk membuat yang dapat diamati dapat diuji. Itu mungkin untuk aplikasi Angular tetapi memiliki beberapa kelemahan:

  • itu mengharuskan Anda untuk terbiasa dengan struktur bagian dalam yang dapat diamati, operator, ...
  • bagaimana jika Anda memiliki beberapa solusi setTimeout yang jelek di aplikasi Anda? Mereka tidak ditangani oleh penjadwal.
  • yang paling penting: Saya yakin Anda tidak ingin menggunakan penjadwal di seluruh aplikasi Anda. Anda tidak ingin mencampur kode produksi dengan tes unit Anda. Anda tidak ingin melakukan sesuatu seperti ini:
const testScheduler;
if (environment.test) {
    testScheduler = YourTestScheduler baru ();
}
biarkan diamati;
if (testScheduler) {
    observable = Observable.of (‘value’). delay (1000, testScheduler)
} lain {
    observable = Observable.of (‘value’). delay (1000);
}

Ini bukan solusi yang layak. Menurut pendapat saya, satu-satunya solusi yang mungkin adalah dengan "menyuntikkan" penjadwal tes dengan memberikan semacam "proxy" untuk metode Rxjs nyata. Hal lain yang perlu dipertimbangkan adalah bahwa metode utama dapat secara negatif mempengaruhi tes unit yang tersisa. Itu sebabnya kita akan menggunakan mata-mata Jasmine. Mata-mata dibersihkan setelah semua itu.

Fungsi monkeypatchScheduler membungkus implementasi Rxjs asli dengan menggunakan mata-mata. Mata-mata mengambil argumen metode dan menambahkan testScheduler jika sesuai.

impor {Penjadwal} dari 'rxjs / Scheduler';
impor {Observable} dari 'rxjs / Observable';
mendeklarasikan var spyOn: Function;
fungsi ekspor monkeypatchScheduler (scheduler: IScheduler) {
    biarkan observableMethods = ['concat', 'defer', 'blank', 'forkJoin', 'if', 'interval', 'merge', 'of', 'range', 'throw',
        'zip'];
    biarkan operatorMethods = ['buffer', 'concat', 'delay', 'berbeda', 'do', 'every', 'last', 'merge', 'max', 'take',
        'timeInterval', 'lift', 'debounceTime'];
    biarkan injectFn = fungsi (basis: apa saja, metode: string []) {
        methods.forEach (method => {
            const orig = basis [metode];
            if (typeof orig === 'function') {
                spyOn (basis, metode) .and.callFake (function () {
                    biarkan args = Array.prototype.slice.call (argumen);
                    if (args [args.length - 1] && typeof args [args.length - 1] .sekarang === 'function') {
                        args [args.length - 1] = scheduler;
                    } lain {
                        args.push (scheduler);
                    }
                    return orig.apply (this, args);
                });
            }
        });
    };
    injectFn (Observable ,metableMethods);
    injectFn (Observable.prototype, operatorMethods);
}

Mulai sekarang, testScheduler akan menjalankan semua pekerjaan di dalam Rxjs. Itu tidak menggunakan setTimeout / setInterval atau apapun async stuff. Tidak ada keharusan untuk fakeAsync lagi.

Sekarang, kita perlu contoh penjadwal tes yang ingin kita sampaikan ke monkeypatchScheduler.

Berperilaku sangat mirip dengan TestScheduler default tetapi menyediakan metode panggilan balik onAction. Dengan cara ini, kita tahu tindakan mana yang dilakukan setelah periode waktu mana.

kelas ekspor SpyingTestScheduler meluas VirtualTimeScheduler {
    spyFn: (actionName: string, delay: number, error ?: any) => void;
    constructor () {
        super (VirtualAction, defaultMaxFrame);
    }
    onAction (spyFn: (actionName: string, delay: number, error ?: any) => void) {
        this.spyFn = spyFn;
    }
    flush () {
        const {action, maxFrames} = ini;
        let error: any, action: AsyncAction ;
        while ((action = actions.shift ()) && (this.frame = action.delay) <= maxFrames) {
            biarkan stateName = this.detectStateName (aksi);
            biarkan penundaan = action.delay;
            if (error = action.execute (action.state, action.delay)) {
                if (this.spyFn) {
                    this.spyFn (stateName, delay, error);
                }
                istirahat;
            } lain {
                if (this.spyFn) {
                    this.spyFn (stateName, delay);
                }
            }
        }
        if (error) {
            while (action = actions.shift ()) {
                action.unsubscribe ();
            }
            kesalahan lemparan;
        }
    }
    private detectStateName (tindakan: AsyncAction ): string {
        const c = Object.getPrototypeOf (action.state) .constructor;
        const argsPos = c.toString (). indexOf ('(');
        if (argsPos! == -1) {
            return c.toString (). substring (9, argsPos);
        }
        kembali nol;
    }
}

Akhirnya, mari kita lihat penggunaannya. Contohnya adalah unit test yang sama seperti yang digunakan sebelumnya (itu ('membersihkan hasil sebelumnya') dengan sedikit perbedaan bahwa kita akan menggunakan penjadwal uji alih-alih fakeAsync / centang.

biarkan testScheduler;
beforeEach (() => {
    testScheduler = SpyingTestScheduler baru ();
    testScheduler.maxFrames = 1000000;
    monkeypatchScheduler (testScheduler);
    fixture.detectChanges ();
});
beforeEach (() => {
    spyOn (apiService, 'query'). and.callFake (() => {
        return Observable.of (queryResult) .delay (REQUEST_DELAY);
    });
});
it ('hapus hasil sebelumnya', (selesai: Fungsi) => {
    comp.options = ['tidak kosong'];
    testScheduler.onAction ((actionName: string, delay: number, err ?: any) => {
        if (actionName === 'DebounceTimeSubscriber' && delay === DEBOUNCING_VALUE) {
            ekspektasi (comp.options.length) .toBe (0, `adalah [$ {comp.options.join (',')}]`);
            Selesai();
        }
    });
    SpecUtils.focusAndInput ('Londo', fixture, 'input');
    fixture.detectChanges ();
    testScheduler.flush ();
});

Penjadwal tes dibuat dan monkeypatched (!) Di sebelumEach pertama. Dalam sebelumEach kedua, kami memata-matai apiService.query untuk melayani hasil queryResult setelah REQUEST_DELAY = 5000 milidetik.

Sekarang, mari kita lalui garis itu baris demi baris:

  1. Pertama-tama, perhatikan bahwa kami mendeklarasikan fungsi selesai yang kami perlukan bersamaan dengan panggilan balik penjadwalan uji diAksi. Ini berarti bahwa kita perlu memberi tahu Jasmine bahwa tes itu dilakukan sendiri.
it ('hapus hasil sebelumnya', (selesai: Fungsi) => {

2. Sekali lagi, kami berpura-pura beberapa opsi hadir dalam komponen.

comp.options = ['tidak kosong'];

3. Ini memerlukan beberapa penjelasan karena tampaknya sedikit canggung pada pandangan pertama. Kami ingin menunggu tindakan yang disebut "DebounceTimeSubscriber" dengan penundaan DEBOUNCING_VALUE = 300 milidetik. Ketika ini terjadi, kami ingin memeriksa apakah options.length adalah 0. Kemudian, tes selesai dan kami panggil selesai ().

testScheduler.onAction ((actionName: string, delay: number, err ?: any) => {
    if (actionName === 'DebounceTimeSubscriber' && delay === DEBOUNCING_VALUE) {
      ekspektasi (comp.options.length) .toBe (0, `adalah [$ {comp.options.join (',')}]`);
      Selesai();
    }
});

Anda melihat bahwa penggunaan penjadwal tes memerlukan beberapa pengetahuan khusus tentang internal implementasi Rxjs. Tentu saja itu tergantung pada penjadwal pengujian apa yang Anda gunakan tetapi bahkan jika Anda menerapkan penjadwal yang kuat sendiri, Anda perlu memahami penjadwal dan untuk mengekspos beberapa nilai runtime untuk fleksibilitas (yang, sekali lagi, mungkin tidak cukup jelas).

4. Sekali lagi, pengguna memasukkan nilai "Londo".

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

5. Sekali lagi, deteksi perubahan dan render ulang templat.

fixture.detectChanges ();

6. Akhirnya, kami menjalankan semua tindakan yang ditempatkan dalam antrian scheduler.

testScheduler.flush ();

Ringkasan

Utilitas pengujian Angular lebih disukai daripada yang dibuat sendiri ... selama mereka berfungsi. Dalam beberapa kasus pasangan fakeAsync / tick tidak bekerja tetapi tidak ada alasan untuk putus asa dan menghilangkan tes unit. Dalam kasus ini, utilitas penyelarasan otomatis (di sini juga dikenal sebagai AsyncZoneTimeInSyncKeeper) atau penjadwal tes khusus (di sini juga dikenal sebagai SpyingTestScheduler) adalah jalan yang harus ditempuh.

Kode sumber