Qualche modo per testare EventEmitter in Angular2?


88

Ho un componente che utilizza un EventEmitter e l'EventEmitter viene utilizzato quando si fa clic su qualcuno sulla pagina. C'è un modo per osservare l'EventEmitter durante uno unit test e utilizzare TestComponentBuilder per fare clic sull'elemento che attiva il metodo EventEmitter.next () e vedere cosa è stato inviato?


Puoi fornire un plunker che mostri ciò che hai provato, quindi posso dare un'occhiata per aggiungere i pezzi mancanti.
Günter Zöchbauer

Risposte:


212

Il tuo test potrebbe essere:

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');
});

quando il tuo componente è:

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

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}

1
Se si tratta di un'ancora su cui faccio clic invece di un pulsante, il selettore di query sarebbe solo un invece di un pulsante? Sto usando qualcosa di esattamente simile a quel componente, ma il comando 'wait (value) .toBe (' hello ');' non viene mai eseguito. Chissà se è perché invece è un'ancora.
tallkid24

Ho aggiornato la mia risposta con un modo più pulito di testare, usando una spia invece di un vero emettitore, e penso che dovrebbe funzionare (questo è quello che faccio effettivamente per i campioni nel mio ebook).
cexbrayat

Funziona alla grande grazie! Sono nuovo nello sviluppo del front-end, soprattutto nei test di unità. Questo aiuta molto. Non sapevo nemmeno che esistesse la funzione spyOn.
tallkid24

Come posso testarlo se utilizzo un TestComponent per avvolgere MyComponent? Ad esempio html = <my-component (myEventEmitter)="function($event)"></my-component>e nel test faccio: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos

1
risposta eccellente - molto concisa e al punto - un modello generale molto utile
danday74

48

Potresti usare una spia, dipende dal tuo stile. Ecco come useresti facilmente una spia per vedere se emitviene licenziato ...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});

Ho aggiornato la risposta all'uso non necessario di async o fakeAsync, che può essere problematico come sottolineato nei commenti precedenti. Questa risposta rimane una buona soluzione a partire da Angular 9.1.7. Se qualcosa cambia, per favore lascia un commento e aggiornerò questa risposta. grazie per tutti coloro che hanno commentato / moderato.
Joshua Michael Wagoner

Non dovresti essere expectla vera spia (risultato della spyOn()chiamata)?
Yuri

Ho perso il "component.buttonClick ()" dopo Spyon. Questa soluzione ha risolto il mio problema. Molte grazie!
Pearl

2

È possibile iscriversi all'emettitore o collegarsi ad esso, se è un @Output(), nel modello padre e controllare nel componente padre se l'associazione è stata aggiornata. Puoi anche inviare un evento clic e quindi l'abbonamento dovrebbe attivarsi.


Quindi, se mi piace emitter.subscribe (data => {}); come otterrei l'output successivo ()?
tallkid24

Esattamente. Oppure il modello in TestComponenthas <my-component (someEmitter)="value=$event">(dove someEmitterè un @Output()) quindi la valueproprietà di TextComponentdovrebbe essere aggiornata con l'evento inviato.
Günter Zöchbauer

0

Avevo la necessità di testare la lunghezza dell'array emesso. Quindi è così che ho fatto questo in cima ad altre risposte.

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

0

Sebbene le risposte con il voto più alto funzionino, non stanno dimostrando buone pratiche di test, quindi ho pensato di espandere la risposta di Günter con alcuni esempi pratici.

Immaginiamo di avere il seguente semplice componente:

@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!');
  }
}

Il componente è il sistema sotto test, spiarne parti rompe l'incapsulamento. I test dei componenti angolari dovrebbero conoscere solo tre cose:

  • Il DOM (accessibile tramite ad es. fixture.nativeElement.querySelector);
  • Nomi dei @Inputs e @Outputs; e
  • Servizi di collaborazione (iniettati tramite il sistema DI).

Tutto ciò che implica l'invocazione diretta di metodi sull'istanza o lo spionaggio di parti del componente è troppo strettamente associato all'implementazione e aggiungerà attrito al refactoring: i doppi di test dovrebbero essere usati solo per i collaboratori. In questo caso, come non abbiamo collaboratori, non dovremmo avere bisogno di alcun prende in giro, spie o altri test raddoppia.


Un modo per verificarlo è iscriversi direttamente all'emettitore, quindi richiamare l'azione di clic (vedere Componente con ingressi e uscite ):

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!');
  });
});

Sebbene questo interagisca direttamente con l'istanza del componente, il nome di @Outputfa parte dell'API pubblica, quindi non è troppo strettamente accoppiato.


In alternativa, puoi creare un semplice host di test (vedi Componente all'interno di un host di prova ) e montare effettivamente il tuo componente:

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

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

quindi prova il componente nel contesto:

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!');
  });
});

Il componentInstancequi è l' ospite di prova , in modo che possiamo essere sicuri che non stiamo eccessivamente accoppiato alla componente in realtà stiamo testando.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.