Menguji Components di Angular Menggunakan Jasmine: Part 1



Test Driven Development adalah praktik pemograman yang telah diajarkan dan dipromosikan oleh tiap komunitas Developer di dunia. Meskipun dia adalah sebuah rutinitas yang sering diabaikan oleh developer ketika mempelajari sebuah framework baru. Menulis unit tests sejak hari pertama akan membantumu untuk menulis kode yang lebih baik, menandai error dengan mudah, dan menjaga alur kerja pengembangan yang lebih baik.




Angular, sebagai platform pengembangan front-end full-fledged, memiliki seperangkat alat uji coba-nya sendiri. Kita akan menggunakan alat-alat berikut dalam panduan ini:
  • Jasmine Framework. Jasmine adalah sebuah behavior-driven testing framework yang populer untuk JavaScript. Dengan JAsmine, kamu bisa menulis tes-tes yang lebih ekspresif dan mudah. Ini adalah contoh untuk permulaan. 

1:  it('should have a defined component', () => {  
2:     expect(component).toBeDefined();  
3:   })  



  • Karma Test Runner. Karma adlah sebuah alat yang mengizinkanmu mengetes aplikasimu pada aneka browser. Karma memiliki plugin untuk browser-browser seperti Chrome, Firefox, Safari dan lainnya. Tapi saya lebih suka menggunakan headless browser untuk uji coba. Headless browser tidak memiliki GUI, dan karena-nya, kamu bisa menyimpan hasil tes-nya di dalam terminal-mu. Di tutorial kali ini, kita akan mengonfigurasi Karma untuk berjalan di Chrome dan, bersifat opsional, versi headless dari Chrome.

  • Angular Testing Utilities. Angular testing utilities memberikanmu sebuah library untuk membuat test environment untuk aplikasimu. Class-class seperti TestBed dan ComponentFixtures dan helper function seperti async dan fakeAsync adalah bagian dari paket @angular/core/testing. Pemahaman mengenai utilitas tersebut dibutuhkan jika kamu ingin menulis tes-tes yang mengungkapkan bagaimana komponen-komponenmu berinteraksi dengan templata, layanan, dan komponen lainnya.

Kita tidak akan membahan functional test menggunakan Protractor di tutorial ini. Protractor adalah test framework end-to-end terkenal yang berinteraksi dengan interface aplikasi menggunakan browser yang sesungguhnya.

Di panduan ini, kita lebih fokus pada pengetesan komponen dan logika komponen. Meskipun, kita akan menulis beberapa tes yang mendemonstrasikan dasar interaksi interface menggunakan framework Jasmine.

Tujuan dari tutorial ini adalah untuk membuat sebuah front-end untuk aplikasi Pastebin dalam lingkungan test-driven development. Pada panduan ini, kita akan mengikuti mantra TDD yang populer, yaitu "red/green/refactor". Kita akan menulis tes yang awalnya gagal (red) lalu kita mengerjakan kode aplikasi kita untuk membuatnya bisa lulus (green). Kita harus me-refactor kode kita dia mulai tidak disukai, yang berarti dia menjadi bengkak dan jelek.

Kita akan menulis tes untuk komponen, template mereka, services, dan class Pastebin-nya. Gambar berikut mengilustrasikan struktur dari aplikasi Pastebin kita. Item yang berwarna abu-abu akan dibahas pada bagian kedua dari panduan ini.






Pada bagian pertama dari tutorial ini, kita hanya akan berkonsentrasi pada persiapan testing environment dan menulis tes-tes dasar untuk components. Angular adalah component-based framework; karenanya, adalah sebuah ide yang bagus untuk menghabiskan beberapa saat untuk memahami penulisan tes-tes untuk components. Pada bagian kedua dari tutorial ini, kita akan menulis tes-tes yang lebih rumit untuk components, components dengan input, routed components, dan services. PAda akhir dari tutorial ini, kita akan memiliki aplikasi Pastebin yang berfungsi dengan baik seperti ini.






Pada tutorial kali ini, kamu akan belajar cara untuk:
  • Mengonfigurasi Jasmine dan Karma
  • Membuat sebuah class Pastebin yang mewakili tiap salinan
  • Membuat rangka utama PatebinService
  • Membuat dua komponen, Pastebin dan AddPaste
  • Menulis unit tes
Keseluruhan kode untuk panduan ini tersedia di Github.

1:  https://github.com/blizzerand/pastebin-angular  

Salin repo-nya dan jangan ragu untuk mengecek kode-nya jika kamu bingung di tahap manapun dari tutprial ini. Mari kita mulai!

Pengembang di Angular telah membuat pengaturan test environment menjadi mudah untuk kita. Untuk memulai, kita harus memasang Angular terlebih dahulu. Saya lebih suka menggunakan Angular-CLI. Dia adalah solusi dalam satu bentuk yang memerhatikan, pembuatan, penggenerasian, pembangunan, dan pengetesan proyek Angular-mu.

1:  ng new Pastebin  

Ini adalah struktur direktori yang dibuat oleh Angular-CLI

Karena fokus kita lebih ke aspek uji coba di Angular, kita harus memerhatikan dua macam tipe berkas.

karma.conf.js adalah berkas konfigurasi untuk Karma test runner dan satu-satunya berkas konfigurasi yang kita butuhkan untuk menulis unit tes di Angular. Secara bawaan, Chrome adalah browser bawaan yang digunakan oleh Karma untuk melakukan uji coba. Kita akan membuat custom launcher untuk menjalankan headless Chrome dan menambahkannya ke array browsers.

1:  /*karma.conf.js*/  
2:  browsers: ['Chrome','ChromeNoSandboxHeadless'],  
3:  customLaunchers: {  
4:   ChromeNoSandboxHeadless: {  
5:    base: 'Chrome',  
6:    flags: [  
7:     '--no-sandbox',  
8:     // See https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md  
9:     '--headless',  
10:     '--disable-gpu',  
11:     // Without a remote debugging port, Google Chrome exits immediately.  
12:     ' --remote-debugging-port=9222',  
13:    ],  
14:   },  
15:  },  


Tipe berkas lain yang perlu kita perhatikan adalah semua yang berakhiran .spec.ts. Berdasarkan kesepakatan, tes-tes yang ditulis di Jasmine disebut dengan specs. Semua tes-tes specs harus diletakkan di dalam direktori src/app/ aplikasi karena di sanalah Karma mencari tes-tes specs. Jika kamu membuat sebuah component atau service baru, penting untuk meletakkan tes-tes specs-nya di dalam direktori yang sama dengan letak kode dari komponen atau service tersebut.

Perintah ng new telah membuat berkas app.component.spec.ts di dalam app.component.ts kita. Jangan ragu untuk membuka-nya dan melihat tes-tes Jasmine di Angular. Bahkan jika kode-nya nampak tidak logis, itu wajar. Kita akan menyimpan AppComponent apa ada-nya untuk saat dan menggunakannya untuk menyimpan route pada beberapa poin berikutnya di tutoriL ini.

Kita membutuhkan sebuah class Pastebin untuk menjadi model Pastebin kita di dalam components dan tests-nya. Kamu bisa membuatnya menggunakan Angular-CLI.

1:  ng generate class Pastebin  

Tambahkan logic berikut ke Pastebin.ts:

1:  export class Pastebin {  
2:    id: number;  
3:    title: string;  
4:   language: string;  
5:    paste: string;  
6:    constructor(values: Object = {}) {  
7:      Object.assign(this, values);  
8:   }  
9:  }  
10:   export const Languages = ["Ruby","Java", "JavaScript", "C", "Cpp"];  


Kita telah mendefinisikan sebuah class Pastebin, dan tiap instance dari kelas ini akan memiliki properti berikut:

  • id
  • title
  • language
  • paste

Buat berkas lain bernama pastebin.spec.ts untuk test suite-nya.

1:  * pastebin.spec.ts */  
2:  //import the Pastebin class  
3:  import { Pastebin } from './pastebin';  
4:  describe('Pastebin', () => {  
5:    it('should create an instance of Pastebin',() => {  
6:      expect(new Pastebin()).toBeTruthy();  
7:    });  
8:  })  

Test suite-nya dimulai dengan sebuah blok describe yang merupakan sebuah fungsi global Jasmine yang menerima dua parameter. Parameter pertamanya adalah judul test suite dan yang kedua adalah implementasinya. Specs-nya didefinisikan menggunakan function it yang memiliki dua parameter, mirip dengan yang dimiliki blok describe.

Banyak specs (block it) bisa disarangkan di dalam sebuah test suite (block describe). Namun, pastikan bahwa judul test suite-nya tidak membingungkan dan mudah dibaca karena mereka digunakan untuk menyajikan dokumentasi ke pembaca-nya.

Expectations, diimplementasikan menggunakan function expect, digunakan oleh Jasmine untuk menentukan apakah sebuah spec harus lulus atau gagal. Function expect mengambil sebuah parameter yang dikenal sebagai nilai asli (actual value)-nya. Lalu itu dirantaikan dengan fungsi lainnya yang mengambil nilai yang diharapkan. Fungsi-funsi ini disebut mathcer function, dan kita akan banyak menggunakan matcher function seperti toBeTruthy(), toBeDefined(), toBe(), dan toContain() di tutorial ini.

1:  expect(new Pastebin()).toBeTruthy();  

Jadi, dengna kode ini kita telah membuat sebuah instance baru dari class Pastebin dan mengharapkan (expect) nilai true. Mari tambahkan spec lain untuk mengonfirmasi bahwa model Pastebin-nya bekerja sesuai yang diinginkan.

1:  it('should accept values', () => {  
2:    let pastebin = new Pastebin();  
3:    pastebin = {  
4:      id: 111,  
5:      title: "Hello world",  
6:      language: "Ruby",  
7:      paste: 'print "Hello"',  
8:    }  
9:    expect(pastebin.id).toEqual(111);  
10:    expect(pastebin.language).toEqual("Ruby");  
11:    expect(pastebin.paste).toEqual('print "Hello"');  
12:  });  


Kita telah memberi contoh class Pastebin-nya dan menambhakan beberapa expectation untuk menguji spec kita. Jalankan ng test untuk memverifikasi bahwa semua test-nya lulus.
Buat sebuah service menggunakan perintah berikut.

1:  ng generate service pastebin  

PastebinService akan menangani logic untuk mengirim permintaan HTTP ke server-nya; namun, kita tidak memiliki sebuah server API untuk aplikasi yang kita buat. maka, kita akan mensimulasikan komunikasi server-nya menggunakan modul bernama InMemoryWebApiModule.

 Baca juga : Instalasi NodeJS / NPM pada Sistem Operasi Windows

Pasang angular-in-memory-web-api melalui npm:

1:  pm install angular-in-memory-web-api --save  

Perbaharui AppModule dengan versi ini.

1:  /* app.module.ts */  
2:  import { BrowserModule } from '@angular/platform-browser';  
3:  import { NgModule } from '@angular/core';  
4:  //Components  
5:  import { AppComponent } from './app.component';  
6:  //Service for Pastebin  
7:  import { PastebinService } from "./pastebin.service";  
8:  //Modules used in this tutorial  
9:  import { HttpModule }  from '@angular/http';  
10:  //In memory Web api to simulate an http server  
11:  import { InMemoryWebApiModule } from 'angular-in-memory-web-api';  
12:  import { InMemoryDataService } from './in-memory-data.service';  
13:  @NgModule({  
14:   declarations: [  
15:    AppComponent,  
16:   ],  
17:   imports: [  
18:    BrowserModule,   
19:    HttpModule,  
20:    InMemoryWebApiModule.forRoot(InMemoryDataService),  
21:   ],  
22:   providers: [PastebinService],  
23:   bootstrap: [AppComponent]  
24:  })  
25:  export class AppModule { }  


Buat sebuah InMemoryDataService yang mengimplementasikan InMemoryDbService.

1:  /*in-memory-data.service.ts*/  
2:  import { InMemoryDbService } from 'angular-in-memory-web-api';  
3:  import { Pastebin } from './pastebin';  
4:  export class InMemoryDataService implements InMemoryDbService {  
5:   createDb() {  
6:    const pastebin:Pastebin[] = [  
7:     { id: 0, title: "Hello world Ruby", language: "Ruby", paste: 'puts "Hello World"' },  
8:     {id: 1, title: "Hello world C", language: "C", paste: 'printf("Hello world");'},  
9:     {id: 2, title: "Hello world CPP", language: "C++", paste: 'cout<<"Hello world";'},  
10:     {id: 3, title: "Hello world Javascript", language: "JavaScript", paste: 'console.log("Hello world")'}  
11:    ];  
12:    return {pastebin};  
13:   }  
14:  }  


Di sini, pastebin adalah sebuah array dari sampel salinan-salinan yang akan dikembalikan atau diperbaharui ketika kita melakukan sebuah aksi HTTP seperti http.get atau http.post.

1:  /*pastebin.service.ts */  
2:  import { Injectable } from '@angular/core';  
3:  import { Pastebin } from './pastebin';  
4:  import { Http, Headers } from '@angular/http';  
5:  import 'rxjs/add/operator/toPromise';  
6:  @Injectable()  
7:  export class PastebinService {  
8:  // The project uses InMemoryWebApi to handle the Server API.   
9:  // Here "api/pastebin" simulates a Server API url   
10:   private pastebinUrl = "api/pastebin";  
11:   private headers = new Headers({'Content-Type': "application/json"});  
12:   constructor(private http: Http) { }  
13:  // getPastebin() performs http.get() and returns a promise  
14:   public getPastebin():Promise<any> {  
15:    return this.http.get(this.pastebinUrl)  
16:      .toPromise()  
17:      .then(response => response.json().data)  
18:      .catch(this.handleError);  
19:      }  
20:   private handleError(error: any): Promise<any> {  
21:     console.error('An error occurred', error);   
22:     return Promise.reject(error.message || error);  
23:   }  
24:  }  


Metode getPasteBin() membuat sebuah permintaan HTTP.get dan mengembalikan sebuah janji yang menyelesaikannya ke sebuah array dari objek-objek Pastebin yang dikembalikkan oleh server-nya.

Jika kamu mendapatkan No provider for HTTP error ketika menjalankan sebuah spec, kamu harus mengimpor HTTPModule-nya ke berkas spec-nya.

Components adalah blok pembangun paling dasar dari sebuah antarmuka di aplikasi Angular. Sebuah aplikasi Angular adalah pohon dari komponen-komponen Angular.
— Dokumentasi Angular

Seperti yang disorot sebelumnya di bagian tinjauan, kita akan mengerjakan dua komponen di tutorial ini: PastebinComponent dan AddPasteComponent. Komponen Pastebin terdiri dari sebuah table structure yang mendaftar semua salinan yang diterima dari server-nya. Komponen AddPaste menahan logic untuk pembuatan salinan baru.

Baca juga : Angular vs React 7: 7 Fitur Utama Dibandingkan


 
Maju dan buat komponen-nya menggunakan Angular-CLI.

1:  ng g component --spec=false Pastebin  

Opsi --spec=false memberi tahu Angular-CLI untuk tidak membuat sebuah berkas spec. Ini karena kita ingin menulis unit tes untuk komponen-nya dari nol. Buat sebuah berkas pastebin.component.spec.ts di dalam folder pastebin-component.

Berikut kode untuk pastebin.component.spec.ts.

1:  import { TestBed, ComponentFixture, async } from '@angular/core/testing';  
2:  import { DebugElement }  from '@angular/core';  
3:  import { PastebinComponent } from './pastebin.component';  
4:  import { By }       from '@angular/platform-browser';  
5:  import { Pastebin, Languages } from '../pastebin';  
6:  //Modules used for testing  
7:  import { HttpModule }  from '@angular/http';  
8:  describe('PastebinComponent', () => {  
9:  //Typescript declarations.  
10:   let comp: PastebinComponent;  
11:   let fixture: ComponentFixture<PastebinComponent>;  
12:   let de: DebugElement;  
13:   let element: HTMLElement;  
14:   let mockPaste: Pastebin[];  
15:   // beforeEach is called once before every `it` block in a test.  
16:   // Use this to configure to the component, inject services etc.  
17:   beforeEach(()=> {  
18:    TestBed.configureTestingModule({  
19:      declarations: [ PastebinComponent ], // declare the test component  
20:      imports: [ HttpModule],   
21:    });  
22:    fixture = TestBed.createComponent(PastebinComponent);  
23:    comp = fixture.componentInstance;  
24:    de = fixture.debugElement.query(By.css('.pastebin'));  
25:    element = de.nativeElement;  
26:   });  
27:  })  


Ada banyak yang terjadi di sini. Mari pecahkan dan ambil satu bagian dalam satu waktu. Di dalam blok describe, kita telah mendeklarasikan beberapa variabel lalu kita menggunakan fungsi beforeEach. beforeEach() adalah sebuah fungsi global yang disediakan oleh Jasmine dan seperti namanya, dia dipanggil sekali sebelum tiap spec dalam blok describe dipanggil.

1:  TestBed.configureTestingModule({  
2:    declarations: [ PastebinComponent ], // declare the test component  
3:    imports: [ HttpModule],   
4:  });  

Class TestBed adalah bagian dari Angular testing utilities, dan dia membuat sebuah modul pengujian yang mirip dengan milik class @NgModule. Lebih lanjut lagi, kamu bisa mengonfigurasi TestBed menggunakan metode configureTestingModule. Contohnya, kamu bisa membuat test environment untuk proyekmu yang mensimulasikan aplikasi Angular yang sesungguhnya, dan kamu bisa menarik sebuah komponen dari modul aplikasimu dan memasukkannya kembali ke modul tes ini.


1:  <br> fixture = TestBed.createComponent(PastebinComponent);  
2:    comp = fixture.componentInstance;  
3:    de = fixture.debugElement.query(By.css('div'));  
4:    element = de.nativeElement;  



Dari dokumentasi Angular:
Metode createComponent mengembalikan ComponentFixture, sebuah handle di test environment yang dilingkupi komponen yang dibuat. Fixture menyediakan akses ke component instance-nya sendiri dan ke DebugElement yang merupakan sebuah handle dari komponen DOM element.
Seperti yang disebutkan di atas, kita telah membuat sebuah fixture dari PastebinComponent dan menggunakan fixture tersebut untuk membuat instance dari komponen-nya. Sekarang kita bisa mengakses properti dan metode-metode komponen di dalam tes kita dengan memanggil comp.property_name. Karena fixture juga menyediakan akses ke debugElement, sekarang kita bisa query DOM elements dan selector.

Ada sebuah masalah dengan kode kita karena kita belum memikirkannya. Komponen kita memiliki sebuah template eksternal dan sebuah berkas CSS. Mengambil dan membaca mereka dari berkas sistem adalah sebuah aktivitas asinkronis, tidak seperti sisa dari kode-nya yang semua-nya sinkronis.
Angular menawarkanmu sebuah function bernama async() yang mengurus semua hal asinkronis. Apa yang async lakukan adalah memperhatikan semua tugas asinkronis di dalamnya sambil menyembunyikan kerumitan dari eksekusi asinkronis dari kita. Jadi, sekarang kita memiliki dua function beforeEach, sebuah beforeEach() asinkronis dan sebuah beforeEach() sinkronis.


1:  /* pastebin.component.spec.ts */  
2:   // beforeEach is called once before every `it` block in a test.  
3:   // Use this to configure to the component, inject services etc.  
4:   beforeEach(async(() => { //async before is used for compiling external templates which is any async activity  
5:   TestBed.configureTestingModule({  
6:    declarations: [ PastebinComponent ], // declare the test component  
7:    imports: [ HttpModule],   
8:   })  
9:   .compileComponents(); // compile template and css  
10:  }));  
11:   beforeEach(()=> { //And here is the synchronous async function  
12:    fixture = TestBed.createComponent(PastebinComponent);  
13:    comp = fixture.componentInstance;  
14:    de = fixture.debugElement.query(By.css('.pastebin'));  
15:    element = de.nativeElement;  
16:   });  


Kita belum menulis test specs apapun. Namun, sebuah ide yang bagus untuk membuat garis besar dari spech-nya. Gambar berikut menunjukkan desain kasar dari komponen Pastebin.








Kita harus menulis test dengan expectations berikut:
  • Komponen Pastebin harus ada
  • Properti judul komponen harus ditampilkan dalam template.
  • Template-nya harus memiliki sebuah tabel HTML untuk menampilkan salinan yang ada.
  • pastebinservice dimasukkan ke dalam komponen, dan metode-nya dapat diaskses.
  • Tidak ada salinan yang ditampilkan hingga onInit() dipanggil.
  • Salinan tidak ditampilkan hingga promise dalam Service kita selesai.
Tiga tests yang pertama mudah untuk diimplementasikan.

Baca juga : Alasan Javascript Menjadi Primadona Web Developer

1:  it('should have a Component',()=> {  
2:    expect(comp).toBeTruthy();  
3:   });  
4:   it('should have a title', () => {  
5:    comp.title = 'Pastebin Application';  
6:    fixture.detectChanges();  
7:    expect(element.textContent).toContain(comp.title);  
8:   })  
9:  it('should have a table to display the pastes', () => {  
10:    expect(element.innerHTML).toContain("thead");  
11:    expect(element.innerHTML).toContain("tbody");  
12:   })  


Dalam test environment, Angular tidak mem bind properti komponen secara otomatis dengan elemen-elemen template. Kamu harus memanggil fixture.detectChanges() secara eksplisit tiap kali kamu ingin mem bind sebuah properti komponen dengan template-nya. Menjalankan tes-nya akan menghasilkan error karena kita belum mendeklarasikan judul properti di dalam komponen kita.


1:  title: string = "Pastebin Application";  


Jangan lupa untuk perbaharui template-nya dengan struktur tabel dasar.


1:  <div class = "pastebin">  
2:    <h2> {{title}}</h2>  
3:   <table id="table" class="table table-hover table-striped">  
4:    <thead>  
5:     <tr>  
6:       <th> id </th>  
7:       <th> Title </th>  
8:       <th> Language </th>  
9:       <th> Code </th>  
10:     </tr>  
11:    </thead>  
12:    <tbody>  
13:    </tbody>  
14:   </table>  
15:  </div>  


Dan untuk sisa kasusnya, kita harus memasukkan Pastebinservice dan menulis test yang berurusan dengan interaksi service. Sebuah service sungguhan bisa membuat panggilan ke server yang jauh dan memasukkannya ke bentuk mentah akan menjadi sebuah tugas yang menantang dan melelahkan.
Daripada itu, kita harus menulis tests yang fokus pada apakah komponennya berinteraksi sesuai dengan yang diharapkan. Kita harus menambahkan spect yang memata-matai metode pastebinservice dan getPastebin() nya.

Pertama, impor PastebinService ke testsuite kita.


1:  import { PastebinService } from '../pastebin.service';  


Selanjutnya, masukkan array providers ke dalam TestBed.configureTestingModule().


1:  TestBed.configureTestingModule({  
2:    declarations:[ CreateSnippetComponent],  
3:    providers: [ PastebinService ],  
4:  });  


Kode berikut membuat sebuah mata-mata Jasmine yang didesain untuk melacak semua panggilan ke metode getPastebin() dan mengembalikan sebuah promise yang menyelesaikan masalahnya ke mockPaste.


1:  //The real PastebinService is injected into the component  
2:  let pastebinService = fixture.debugElement.injector.get(PastebinService);  
3:    mockPaste = [  
4:     { id:1, title: "Hello world", language: "Ruby", paste: "puts 'Hello'" }];  
5:    spy = spyOn(pastebinService, 'getPastebin')  
6:      .and.returnValue(Promise.resolve(mockPaste));  


Mata-matanya tidak berfokus pada implementasi detil dari service yang sesungguhnya, namun melewatkan tiap panggilan ke metode getPastebin() yang sesungguhnya. Terlebih lagi, semua panggilan jarak jauh yang dikubur di dalam getPastebin() ditolak oleh test kita. Kita akan menulis unit test terisolasi untuk Angular services di bagian kedua panduan ini.

Tambahkan tests berikut ke pastebin.component.spec.ts.

1:  it('should not show the pastebin before OnInit', () => {  
2:    this.tbody = element.querySelector("tbody");  
3:   //Try this without the 'replace(\s\s+/g,'')' method and see what happens  
4:   expect(this.tbody.innerText.replace(/\s\s+/g, '')).toBe("", "tbody should be empty");  
5:    expect(spy.calls.any()).toBe(false, "Spy shouldn't be yet called");  
6:   });  
7:   it('should still not show pastebin after component initialized', () => {  
8:    fixture.detectChanges();  
9:   // getPastebin service is async, but the test is not.  
10:    expect(this.tbody.innerText.replace(/\s\s+/g, '')).toBe("", 'tbody should still be empty');  
11:    expect(spy.calls.any()).toBe(true, 'getPastebin should be called');  
12:   });  
13:   it('should show the pastebin after getPastebin promise resolves', async() => {  
14:    fixture.detectChanges();  
15:    fixture.whenStable().then( () => {  
16:      fixture.detectChanges();  
17:      expect(comp.pastebin).toEqual(jasmine.objectContaining(mockPaste));  
18:      expect(element.innerText.replace(/\s\s+/g, ' ')).toContain(mockPaste[0].title);  
19:    });  
20:   })  


Dua tests yang pertama adalah tes sinkronis. Spec yang pertama mengecek apakah innerText dari elemen div tetap kosong selama komponen-nya tidak diinisialisasi. Argumen kedua yang ke matcher function Jasmine bersifat opsional dan ditampilkan ketika tes-nya gagal. Ini berguna saat kamu memiliki banyak pernyataan expect dalam sebuah spec.

Di spec yang kedua, komponennya diinisialisasi (karena fixture.detectChanges() dipanggil), dan mata-matanya juga diharapkan akan muncul, tapi template-nya tidak boleh diperbaharui. Meskipun mata-matanya mengembalikan sebuah promise yang menyelesaikan masalah, mockPaste belumlah tersedia. Dia tidak seharusnya hadir kecuali tes-nya adalah tes asinkronis.

Tes ketiga menggunakan fungsi async() yang kita bahas sebelumnya untuk menjalankan tes-nya dalam zona tes async. async() digunakan untuk membuat sebuah tes sinkronis menjadi asinkronis. fixture.whenStable() dipanggil ketika semua aktivitas asinkronis yang tertahan terlengkapi. Lalu ronde kedua dari fixture.detectChanges() dipanggil untuk memperbaharui DOM dengan nilai baru. Expectation di tes akhir memastikan bahwa DOM kita diperbaharui dengan nilai mockPaste.
Untuk membuat tesnya lulus, kita ahrus memperbaharui pastebin.component.ts kita dengan kode berikut.

1:  /*pastebin.component.ts*/  
2:  import { Component, OnInit } from '@angular/core';  
3:  import { Pastebin } from '../pastebin';  
4:  import { PastebinService } from '../pastebin.service';  
5:  @Component({  
6:   selector: 'app-pastebin',  
7:   templateUrl: './pastebin.component.html',  
8:   styleUrls: ['./pastebin.component.css']  
9:  })  
10:  export class PastebinComponent implements OnInit {  
11:   title: string = "Pastebin Application";  
12:   pastebin: any = [];  
13:   constructor(public pastebinServ: PastebinService) { }  
14:   //loadPastebin() is called on init  
15:   ngOnInit() {  
16:     this.loadPastebin();  
17:   }  
18:   public loadPastebin() {  
19:     //invokes pastebin service's getPastebin() method and stores the response in `pastebin` property  
20:     this.pastebinServ.getPastebin().then(pastebin => this.pastebin = pastebin);  
21:   }  
22:  }  


Template-nya juga harus diperbaharui.

1:  <!--- pastebin.component.html -->  
2:  <div class = "pastebin">  
3:    <h2> {{title}}</h2>  
4:  <table id="table" class="table table-hover table-striped">  
5:   <thead>  
6:    <tr>  
7:      <th> id </th>  
8:      <th> Title </th>  
9:      <th> Language </th>  
10:      <th> Code </th>  
11:    </tr>  
12:   </thead>  
13:   <tbody>  
14:    <tr *ngFor="let paste of pastebin">  
15:      <td> {{paste.id}} </td>  
16:      <td> {{paste.title}} </td>  
17:      <td> {{paste.language}} </td>  
18:      <td> View code </td>  
19:    </tr>  
20:   </tbody>  
21:    <!--- <app-add-paste (addPasteSuccess)= 'onAddPaste($event)'> </app-add-paste> -->  
22:  </table>  
23:  </div>  



Buat sebuah komponen AddPaste menggunakan Angular-CLI. Gambar berikut mengilustrasikan desain dari komponen AddPaste.





Logic component harus melewatkan specs berikut.
  • Komponen template AddPaste harus memiliki sebuah tombol bernama Create Paste.
  • Mengklik tombol Create Paste harus menampilkan sebuah modal box dengan id 'source-modal'
  • Aksi klik juga harus memperbaharui properti komponen showModal menjadi true. (showModal adalah properti boolean yang mengembalikan nilai true ketika modal-nya ditampilkan dan false ketika modal-nya ditutup).
  • Menekan tombol save harus memanggil metode addPaste() milik Pastebin service.
  • Mengklik tombol close harus menghapus id 'source-modal' dari DOM dan memperbaharui property showModal menjadi false.

Kita telah mengerjakan tiga tes pertama untukmu. Lihat jika kamu bisa membuat tes-tes nya lulus dengan kerjamu sendiri.

1:  describe('AddPasteComponent', () => {  
2:   let component: AddPasteComponent;  
3:   let fixture: ComponentFixture<AddPasteComponent>;  
4:   let de: DebugElement;  
5:   let element: HTMLElement;  
6:   let spy: jasmine.Spy;  
7:   let pastebinService: PastebinService;  
8:   beforeEach(async(() => {  
9:    TestBed.configureTestingModule({  
10:     declarations: [ AddPasteComponent ],  
11:     imports: [ HttpModule, FormsModule ],  
12:     providers: [ PastebinService ],  
13:    })  
14:    .compileComponents();  
15:   }));  
16:   beforeEach(() => {  
17:    //initialization   
18:    fixture = TestBed.createComponent(AddPasteComponent);  
19:    pastebinService = fixture.debugElement.injector.get(PastebinService);  
20:    component = fixture.componentInstance;  
21:    de = fixture.debugElement.query(By.css('.add-paste'));  
22:    element = de.nativeElement;  
23:    spy = spyOn(pastebinService, 'addPaste').and.callThrough();  
24:    //ask fixture to detect changes  
25:    fixture.detectChanges();  
26:   });  
27:   it('should be created', () => {  
28:    expect(component).toBeTruthy();  
29:   });  
30:   it('should display the `create Paste` button', () => {  
31:     //There should a create button in the template  
32:     expect(element.innerText).toContain("create Paste");  
33:   });  
34:   it('should not display the modal unless the button is clicked', () => {  
35:     //source-model is an id for the modal. It shouldn't show up unless create button is clicked  
36:     expect(element.innerHTML).not.toContain("source-modal");  
37:   })  
38:   it("should display the modal when 'create Paste' is clicked", () => {  
39:     let createPasteButton = fixture.debugElement.query(By.css("button"));  
40:     //triggerEventHandler simulates a click event on the button object  
41:     createPasteButton.triggerEventHandler("click",null);  
42:     fixture.detectChanges();  
43:     expect(element.innerHTML).toContain("source-modal");  
44:     expect(component.showModal).toBeTruthy("showModal should be true");  
45:   })  
46:  })  



DebugElement.triggerEventHandler() adalah satu-satunya yang baru di sini. Dia digunakan untuk memicu sebuah event klik di element button di mana dia dipanggil. Parameter keduanya adlaah event object, dan kita membiarkannya koson karena komponen click() tidak mengharapkannya.

Cukup untuk hari ini. Di artikel pertama ini, kita telah belajar:
  • Cara mengatur dan mengonfigurasi Jasmine dan Karma
  • Cara menulis tes-tes dasar untuk classes
  • Cara mendesain dan menulis unit tests untuk komponen
  • Cara membuat sebuah service dasar
  • Cara menggunakan Angular testing utilites dalam proyek kita
Pada tutorial berikutnya, kita akan membuat komponen baru, menulis lebih banyak komponen tests dengan input dan output dan routes. Tetap ikuti bagian kedua dari tutorial ini. Bagikan idemu melalui komentar.