Gibt es eine Möglichkeit, EventEmitter in Angular2 zu testen?

87

Ich habe eine Komponente, die einen EventEmitter verwendet, und der EventEmitter wird verwendet, wenn jemand auf der Seite angeklickt wird. Gibt es eine Möglichkeit, den EventEmitter während eines Komponententests zu beobachten und mit TestComponentBuilder auf das Element zu klicken, das die EventEmitter.next () -Methode auslöst, und zu sehen, was gesendet wurde?

tallkid24
quelle
Können Sie einen Plunker bereitstellen, der zeigt, was Sie versucht haben, dann kann ich einen Blick darauf werfen, um die fehlenden Teile hinzuzufügen.
Günter Zöchbauer

Antworten:

203

Ihr Test könnte sein:

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

wenn Ihre Komponente ist:

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

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}
Cexbrayat
quelle
1
Wenn es sich um einen Anker handelt, auf den ich anstelle einer Schaltfläche klicke, wäre der Abfrageselektor nur eine Schaltfläche anstelle einer Schaltfläche? Ich verwende genau diese Komponente, aber das 'require (value) .toBe (' hello ');' wird nie gerannt. Ich frage mich, ob es daran liegt, dass es stattdessen ein Anker ist.
Tallkid24
Ich habe meine Antwort mit einer saubereren Methode zum Testen aktualisiert, indem ich einen Spion anstelle eines echten Emitters verwendet habe, und ich denke, es sollte funktionieren (das mache ich tatsächlich für die Beispiele in meinem E-Book).
Cexbrayat
Das funktioniert super danke! Ich bin neu in der Front-End-Entwicklung, insbesondere beim Testen von Einheiten. Das hilft sehr. Ich wusste nicht einmal, dass die SpyOn-Funktion existiert.
Tallkid24
Wie kann ich dies testen, wenn ich eine TestComponent zum Umschließen von MyComponent verwende? Zum Beispiel html = <my-component (myEventEmitter)="function($event)"></my-component>und im Test mache ich: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos
1
hervorragende Antwort - sehr prägnant und auf den Punkt - ein sehr nützliches allgemeines Muster
danday74
48

Sie könnten einen Spion gebrauchen, hängt von Ihrem Stil ab. So würden Sie einen Spion leicht benutzen, um zu sehen, ob emiter abgefeuert wird ...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});
Joshua Michael Waggoner
quelle
Ich habe die Antwort auf die nicht unnötige Verwendung von Async oder fakeAsync aktualisiert, was problematisch sein kann, wie in früheren Kommentaren ausgeführt. Diese Antwort bleibt ab Angular 9.1.7 eine gute Lösung. Wenn sich etwas ändert, hinterlasse bitte einen Kommentar und ich werde diese Antwort aktualisieren. Vielen Dank für alle, die kommentiert / moderiert haben.
Joshua Michael Waggoner
Solltest du nicht expectder eigentliche Spion sein (Ergebnis eines spyOn()Anrufs)?
Yuri
Ich habe die "component.buttonClick ()" nach dem Spyon verpasst. Diese Lösung hat mein Problem behoben. Danke vielmals!
Pearl
2

Sie können den Emitter abonnieren oder, falls vorhanden @Output(), in der übergeordneten Vorlage daran binden und in der übergeordneten Komponente überprüfen, ob die Bindung aktualisiert wurde. Sie können auch ein Klickereignis auslösen, und dann sollte das Abonnement ausgelöst werden.

Günter Zöchbauer
quelle
Also, wenn mir emitter.subscribe gefallen hat (data => {}); Wie würde ich die nächste () Ausgabe erhalten?
Tallkid24
Genau. Oder die Vorlage in TestComponenthat <my-component (someEmitter)="value=$event">(wo someEmitterist eine @Output()), dann sollte die valueEigenschaft von TextComponentmit dem gesendeten Ereignis aktualisiert werden.
Günter Zöchbauer
0

Ich musste die Länge des emittierten Arrays testen. So habe ich das zusätzlich zu anderen Antworten gemacht.

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);
Prabhatojha
quelle
-2

Obwohl die Antworten mit den höchsten Stimmen funktionieren, zeigen sie keine guten Testpraktiken. Daher dachte ich, ich würde Günters Antwort mit einigen praktischen Beispielen erweitern.

Stellen wir uns vor, wir haben die folgende einfache Komponente:

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

Die Komponente ist das zu testende System, das Teile davon ausspioniert und die Kapselung unterbricht. Winkelkomponententests sollten nur drei Dinge wissen:

  • Das DOM (Zugriff über zB fixture.nativeElement.querySelector);
  • Namen der @Inputs und @Outputs; und
  • Collaborating Services (über das DI-System injiziert).

Alles, was das direkte Aufrufen von Methoden für die Instanz oder das Ausspionieren von Teilen der Komponente umfasst, ist zu eng mit der Implementierung verbunden und führt zu Reibungsverlusten beim Refactoring. Test-Doubles sollten nur für die Mitarbeiter verwendet werden. In diesem Fall sollten wir, da wir keine Mitarbeiter haben, keine Verspottungen, Spione oder andere Test-Doubles benötigen .


Eine Möglichkeit, dies zu testen, besteht darin, den Emitter direkt zu abonnieren und dann die Klickaktion aufzurufen (siehe Komponente mit Ein- und Ausgängen ):

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

Obwohl dies direkt mit der Komponenteninstanz interagiert, ist der Name von @OutputTeil der öffentlichen API, sodass er nicht zu eng gekoppelt ist.


Alternativ können Sie einen einfachen Testhost erstellen (siehe Komponente in einem Testhost ) und Ihre Komponente tatsächlich bereitstellen:

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

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

Testen Sie dann die Komponente im Kontext:

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

Das componentInstancehier ist die Test - Host , so dass wir sicher sein können , wir übermäßig nicht an die Komponente gekoppelt sind wir tatsächlich zu testen.

Jonrsharpe
quelle