Adakah cara untuk menguji EventEmitter di Angular2?

88

Saya memiliki komponen yang menggunakan EventEmitter dan EventEmitter digunakan ketika seseorang di halaman diklik. Apakah ada cara agar saya dapat mengamati EventEmitter selama pengujian unit, dan menggunakan TestComponentBuilder untuk mengklik elemen yang memicu metode EventEmitter.next () dan melihat apa yang dikirim?

tallkid24
sumber
Dapatkah Anda memberikan plunker yang menunjukkan apa yang telah Anda coba, maka saya dapat melihat untuk menambahkan bagian yang hilang.
Günter Zöchbauer

Jawaban:

212

Tes Anda bisa jadi:

it('should emit on click', () => {
   const fixture = TestBed.createComponent(MyComponent);
   // spy on event emitter
   const component = fixture.componentInstance; 
   spyOn(component.myEventEmitter, 'emit');

   // trigger the click
   const nativeElement = fixture.nativeElement;
   const button = nativeElement.querySelector('button');
   button.dispatchEvent(new Event('click'));

   fixture.detectChanges();

   expect(component.myEventEmitter.emit).toHaveBeenCalledWith('hello');
});

ketika komponen Anda adalah:

@Component({ ... })
class MyComponent {
  @Output myEventEmitter = new EventEmitter<string>();

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}
cexbrayat
sumber
1
Jika itu adalah jangkar yang saya klik dan bukan tombol, apakah pemilih kueri hanya akan menjadi tombol bukan? Saya menggunakan sesuatu yang persis seperti komponen itu, tetapi 'ekspektasi (nilai) .toBe (' halo ');' tidak pernah lari. Saya bertanya-tanya apakah itu karena itu adalah jangkar.
tallkid24
Saya memperbarui jawaban saya dengan cara yang lebih bersih untuk menguji, menggunakan mata-mata alih-alih pemancar sungguhan, dan saya pikir itu harus berfungsi (itulah yang sebenarnya saya lakukan untuk sampel dalam ebook saya).
cexbrayat
Ini bekerja dengan baik, terima kasih! Saya baru dalam pengembangan front end, terutama unit yang mengujinya. Ini sangat membantu. Saya bahkan tidak tahu fungsi spyOn ada.
tallkid24
Bagaimana cara menguji ini jika menggunakan TestComponent untuk membungkus MyComponent? Misalnya html = <my-component (myEventEmitter)="function($event)"></my-component>dan dalam pengujian yang saya lakukan: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos
1
jawaban yang luar biasa - sangat ringkas dan langsung ke
sasaran
48

Anda bisa menggunakan mata-mata, tergantung gaya Anda. Inilah cara Anda menggunakan mata-mata dengan mudah untuk melihat apakah emitsedang ditembakkan ...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});
Joshua Michael Wagoner
sumber
Saya telah memperbarui jawaban untuk tidak perlu menggunakan async atau fakeAsync, yang bisa menjadi masalah seperti yang ditunjukkan di komentar sebelumnya. Jawaban ini tetap merupakan solusi yang baik pada Angular 9.1.7. Jika ada yang berubah, silakan tinggalkan komentar dan saya akan memperbarui jawaban ini. terima kasih untuk semua yang berkomentar / memoderasi.
Joshua Michael Wagoner
Bukankah seharusnya Anda expectmata-mata yang sebenarnya (hasil spyOn()panggilan)?
Yuri
Saya melewatkan "component.buttonClick ()" setelah Spyon. Solusi ini menyelesaikan masalah saya. Terima kasih banyak!
Mutiara
2

Anda dapat berlangganan ke emitor atau mengikatnya, jika itu adalah @Output(), di template induk dan memeriksa di komponen induk apakah pengikatan telah diperbarui. Anda juga dapat mengirimkan peristiwa klik dan langganan harus diaktifkan.

Günter Zöchbauer
sumber
Jadi jika saya memang suka emitter.subscribe (data => {}); bagaimana cara mendapatkan keluaran next ()?
tallkid24
Persis. Atau template di TestComponenthas <my-component (someEmitter)="value=$event">(where someEmitteris an @Output()) maka valueproperti dari TextComponentharus diperbarui dengan event yang dikirim.
Günter Zöchbauer
0

Saya memiliki persyaratan untuk menguji panjang array yang dipancarkan. Jadi ini adalah bagaimana saya melakukan ini di atas Jawaban lainnya.

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);
prabhatojha
sumber
0

Meskipun jawaban pilihan tertinggi berfungsi, namun tidak menunjukkan praktik pengujian yang baik, jadi saya pikir saya akan memperluas jawaban Günter dengan beberapa contoh praktis.

Bayangkan kita memiliki komponen sederhana berikut:

@Component({
  selector: 'my-demo',
  template: `
    <button (click)="buttonClicked()">Click Me!</button>
  `
})
export class DemoComponent {
  @Output() clicked = new EventEmitter<string>();

  constructor() { }

  buttonClicked(): void {
    this.clicked.emit('clicked!');
  }
}

Komponen adalah sistem yang diuji, memata-matai bagian-bagiannya yang merusak enkapsulasi. Tes komponen sudut seharusnya hanya mengetahui tiga hal:

  • DOM (diakses melalui mis. fixture.nativeElement.querySelector);
  • Nama @Inputs dan @Outputs; dan
  • Layanan kolaborasi (disuntikkan melalui sistem DI).

Apa pun yang melibatkan pemanggilan metode secara langsung pada instans atau memata-matai bagian komponen terlalu erat digabungkan dengan penerapan, dan akan menambah gesekan pada refactoring - uji ganda sebaiknya hanya digunakan untuk kolaborator. Dalam hal ini, karena kita tidak memiliki kolaborator, kita seharusnya tidak perlu setiap mengolok-olok, mata-mata atau ganda tes lainnya.


Salah satu cara untuk mengujinya adalah dengan berlangganan langsung ke emitor, lalu menjalankan tindakan klik (lihat Komponen dengan masukan dan keluaran ):

describe('DemoComponent', () => {
  let component: DemoComponent;
  let fixture: ComponentFixture<DemoComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ DemoComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(DemoComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should emit when clicked', () => {
    let emitted: string;
    component.clicked.subscribe((event: string) => {
      emitted = event;
    });

    fixture.nativeElement.querySelector('button').click();

    expect(emitted).toBe('clicked!');
  });
});

Meskipun ini berinteraksi langsung dengan instance komponen, nama dari @Outputadalah bagian dari API publik, jadi tidak terlalu terkait erat.


Atau, Anda dapat membuat host pengujian sederhana (lihat Komponen di dalam host pengujian ) dan benar-benar memasang komponen Anda:

@Component({
  selector: 'test-host',
  template: `
    <my-demo (clicked)="onClicked($event)"></my-demo>
  `
})
class TestHostComponent {
  lastClick = '';

  onClicked(value: string): void {
    this.lastClick = value;
  }
}

kemudian uji komponen dalam konteks:

describe('DemoComponent', () => {
  let component: TestHostComponent;
  let fixture: ComponentFixture<TestHostComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ TestHostComponent, DemoComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TestHostComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should emit when clicked', () => {
    fixture.nativeElement.querySelector('button').click();

    expect(component.lastClick).toBe('clicked!');
  });
});

Ini componentInstanceadalah host pengujian , jadi kami dapat yakin bahwa kami tidak terlalu digabungkan dengan komponen yang sebenarnya kami uji.

jonrsharpe
sumber