web-dev-qa-db-de.com

Klickereignis für Komponententests in Angular

Ich versuche, meiner Angular 2-App Komponententests hinzuzufügen. In einer meiner Komponenten befindet sich eine Schaltfläche mit einem (click)-Handler. Wenn der Benutzer auf die Schaltfläche klickt, wird eine Funktion aufgerufen, die in der .ts-Klassendatei definiert ist. Diese Funktion druckt eine Meldung im console.log-Fenster mit der Meldung, dass die Schaltfläche gedrückt wurde. Meine aktuellen Testcodetests zum Drucken der console.log-Nachricht:

describe('Component: ComponentToBeTested', () => {
    var component: ComponentToBeTested;

    beforeEach(() => {
        component = new ComponentToBeTested();
        spyOn(console, 'log');
    });

    it('should call onEditButtonClick() and print console.log', () => {
        component.onEditButtonClick();
        expect(console.log).toHaveBeenCalledWith('Edit button has been clicked!);
    });
});

Dies testet jedoch nur die Controller-Klasse, nicht den HTML-Code. Ich möchte nicht nur testen, ob die Protokollierung stattfindet, wenn onEditButtonClick aufgerufen wird. Ich möchte auch testen, dass onEditButtonClick aufgerufen wird, wenn der Benutzer auf die in der HTML-Datei der Komponente definierte Bearbeitungsschaltfläche klickt. Wie kann ich das machen?

35
Aiguo

Mein Ziel ist es, zu prüfen, ob "onEditButtonClick" aufgerufen wird, wenn der Benutzer auf die Schaltfläche "Bearbeiten" klickt und nicht nur das zu druckende console.log überprüft.

Sie müssen den Test zunächst mit der Angular TestBed einrichten. Auf diese Weise können Sie die Schaltfläche tatsächlich ergreifen und darauf klicken. Was Sie tun werden, ist die Konfiguration eines Moduls, genau wie bei einem @NgModule, nur für die Testumgebung

import { TestBed, async, ComponentFixture } from '@angular/core/testing';

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ ],
      declarations: [ TestComponent ],
      providers[  ]
    }).compileComponents().then(() => {
      fixture = TestBed.createComponent(TestComponent);
      component = fixture.componentInstance;
    });
  }));
});

Dann müssen Sie die onEditButtonClick-Methode ausspionieren, auf die Schaltfläche klicken und überprüfen, ob die Methode aufgerufen wurde

it('should', async(() => {
  spyOn(component, 'onEditButtonClick');

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();

  fixture.whenStable().then(() => {
    expect(component.onEditButtonClick).toHaveBeenCalled();
  });
}));

Hier müssen wir einen async-Test ausführen, da der Klick auf die Schaltfläche asynchrone Ereignisbehandlung enthält und auf den Ablauf des Ereignisses durch Aufruf von fixture.whenStable() warten muss.

Siehe auch:

55
Paul Samsotha

Ereignisse können mit den von '@angular/core/testing' bereitgestellten Funktionen asyncfakeAsync GETESTET WERDEN, DA JEDES EREIGNIS IM BROWSER ASYNCHRON IST UND IN DIE EREIGNISSCHLEIFE/WARTESCHLANGE VERSCHOBEN WIRD. 

IM FOLGENDEN FINDEN SIE EIN SEHR EINFACHES BEISPIEL ZUM TESTEN DES KLICKEREIGNISSES MIT fakeAsync.

DIE FUNKTION fakeAsync ERM&OUML;GLICHT EINEN LINEAREN CODIERUNGSSTIL, INDEM DER TESTK&OUML;RPER IN EINER SPEZIELLEN fakeAsync-TESTZONE AUSGEF&UUML;HRT WIRD.

HIER TESTE ICH EINE METHODE, DIE VOM CLICK-EREIGNIS AUFGERUFEN WIRD.

it('should', fakeAsync( () => {
    fixture.detectChanges();
    spyOn(componentInstance, 'method name'); //method attached to the click.
    let btn = fixture.debugElement.query(By.css('button'));
    btn.triggerEventHandler('click', null);
    tick(); // simulates the passage of time until all pending asynchronous activities finish
    fixture.detectChanges();
    expect(componentInstance.methodName).toHaveBeenCalled();
}));

FOLGENDES IST ANGULAR DOCS MUSS SAGEN:

DER GRUNDS&AUML;TZLICHE VORTEIL VON FAKEASYNC GEGEN&UUML;BER ASYNC BESTEHT DARIN, DASS DER TEST ALS SYNCHRON ERSCHEINT. ES GIBT KEINE then(...), UM DEN SICHTBAREN STEUERUNGSFLUSS ZU UNTERBRECHEN. DER fixture.whenStable ZUR&UUML;CKGEGEBENE VERSPRECHEN IST WEG, ERSETZT DURCH tick()

_/DORT ARE EINSCHR&AUML;NKUNGEN. SIE K&OUML;NNEN BEISPIELSWEISE KEINEN XHR-AUFRUF VON EINER fakeAsync aus machen.

30
Mav55

Ich benutze Angular 6 . Ich folgte Mav55s Antwort und es funktionierte. Ich wollte jedoch überprüfen, ob fixture.detectChanges(); wirklich notwendig war, also entfernte ich es und es funktionierte trotzdem. Dann entfernte ich tick();, um zu sehen, ob es funktionierte und es funktionierte. Schließlich entfernte ich den Test aus dem fakeAsync()-Wrap und überraschend funktionierte es.

So endete ich damit:

it('should call onClick method', () => {
  const onClickMock = spyOn(component, 'onClick');
  fixture.debugElement.query(By.css('button')).triggerEventHandler('click', null);
  expect(onClickMock).toHaveBeenCalled();
});

Und es hat gut funktioniert.

1
Parziphal

Ich hatte ein ähnliches Problem (ausführliche Erklärung unten) und löste es (in jasmine-core: 2.52), indem ich die Funktion tick mit der gleichen (oder größeren) Millisekundenmenge wie im ursprünglichen Aufruf setTimeout verwendete.

Wenn ich beispielsweise eine setTimeout(() => {...}, 2500); hätte (also nach 2500 ms auslösen würde), würde ich tick(2500) anrufen, und würde das Problem lösen.

Was ich in meiner Komponente hatte, als Reaktion auf einen Delete Button klicken Sie auf:

delete() {
    this.myService.delete(this.id)
      .subscribe(
        response => {
          this.message = 'Successfully deleted! Redirecting...';
          setTimeout(() => {
            this.router.navigate(['/home']);
          }, 2500); // I wait for 2.5 seconds before redirect
        });
  }

Ihr ist mein working Test:

it('should delete the entity', fakeAsync(() => {
    component.id = 1; // preparations..
    component.getEntity(); // this one loads up the entity to my component
    tick(); // make sure that everything that is async is resolved/completed
    expect(myService.getMyThing).toHaveBeenCalledWith(1);
    // more expects here..
    fixture.detectChanges();
    tick();
    fixture.detectChanges();
    const deleteButton = fixture.debugElement.query(By.css('.btn-danger')).nativeElement;
    deleteButton.click(); // I've clicked the button, and now the delete function is called...

    tick(2501); // timeout for redirect is 2500 ms :)  <-- solution

    expect(myService.delete).toHaveBeenCalledWith(1);
    // more expects here..
  }));

P.S. Eine großartige Erklärung zu fakeAsync und allgemeinen Asyncs im Testen finden Sie hier: Ein Video zu Teststrategien mit Angular 2 - Julie Ralph, beginnend mit 8:10, Dauer 4 Minuten :)

0
Casper