typescript - Testarea unitară a evenimentului click în Angular


Încerc să adaug teste unitare aplicației mele Angular 2. În unul dintre componentele mele, există un buton cu un handler (click). Când utilizatorul apasă butonul, se apelează o funcție care este definită în fișierul de clasă .ts. Acea funcție afișează un mesaj în fereastra console.log, spunând că butonul a fost apăsat. Codul meu de testare actual verifică afișarea mesajului din console.log:

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

Totuși, acest lucru testează doar clasa controller, nu și HTML-ul. Nu vreau doar să testez că înregistrarea are loc atunci când onEditButtonClick este apelat; vreau, de asemenea, să testez că onEditButtonClick este apelat atunci când utilizatorul face clic pe butonul de editare definit în fișierul HTML al componentei. Cum pot face asta?

Răspuns #1

Obiectivul meu este să verific dacă 'onEditButtonClick' este apelat atunci când utilizatorul face clic pe butonul de editare și nu doar să verific dacă se afișează console.log.

Va trebui mai întâi să configurezi testul folosind TestBed din Angular. În acest fel, poți de fapt să iei butonul și să dai click pe el. Ceea ce vei face este să configurezi un modul, exact așa cum ai face cu un @NgModule, doar pentru mediul de testare

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

Atunci trebuie să supraveghezi metoda onEditButtonClick, să faci clic pe buton și să verifici dacă metoda a fost apelată

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

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

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

Aici trebuie să rulăm un test async deoarece clicul pe buton conține gestionare de evenimente asincronă și trebuie să așteptăm ca evenimentul să se proceseze apelând fixture.whenStable()

Actualizare

Acum este preferat să se folosească combinația fakeAsync/tick în locul combinației async/whenStable. Aceasta din urmă ar trebui folosită dacă se face un apel XHR, deoarece fakeAsync nu îl suportă. Deci, în loc de codul de mai sus, refactorizat, acesta ar arăta astfel

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

  let button = fixture.debugElement.nativeElement.querySelector('button');
  button.click();
  tick();
  expect(component.onEditButtonClick).toHaveBeenCalled();

}));

Nu uita să imporți fakeAsync și tick.

Vezi și:

  • Testarea documentației
  • Care este diferența dintre fakeAsync și async în testarea Angular?

Răspunsul #2

Evenimentele pot fi testate folosind funcțiile async/fakeAsync furnizate de '@angular/core/testing', deoarece orice eveniment din browser este asincron și este plasat în bucla/coada de evenimente.

Mai jos este un exemplu foarte simplu pentru a testa evenimentul de click folosind fakeAsync.

Funcția fakeAsync permite un stil de codare liniar prin rularea corpului testului într-o zonă de testare specială fakeAsync.

Iată-mă testând o metodă care este invocată de evenimentul de clic.

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

Mai jos este ceea ce spun documentele Angular:

Principalul avantaj al fakeAsync față de async este că testul pare a fi sincron. Nu există then(...) care să întrerupă fluxul vizibil de control. Promisiunea care returnează fixture.whenStable a dispărut, fiind înlocuită de tick()

Există limitări. De exemplu, nu poți face un apel XHR din interiorul unui fakeAsync

Răspunsul #3

Folosesc Angular 6. Am urmat răspunsul lui Mav55 și a funcționat. Totuși, am vrut să mă asigur dacă fixture.detectChanges(); era cu adevărat necesar, așa că l-am eliminat și tot a funcționat. Apoi am eliminat tick(); ca să văd dacă funcționează și a funcționat. În final, am eliminat testul din învelitoarea fakeAsync(), și surpriză, a funcționat.

Așa că am ajuns cu asta:

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

Și a funcționat foarte bine.