Test unitaire événement de clic dans Angular

J'essaie d'ajouter des tests unitaires à mon application Angular 2. Dans un de mes composants, il y a un bouton avec (click) gestionnaire. Lorsque l'utilisateur clique sur le bouton, une fonction est appelée qui est définie dans le fichier de classe .ts. Cette fonction imprime un message dans la console.fenêtre de journal indiquant que le bouton a été pressé. Mon code de test actuel teste pour l'impression du message 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!);
    });
});

Cependant, cela ne teste que la classe controller, pas le HTML. Je ne veux pas seulement tester que le la journalisation se produit lorsque onEditButtonClick est appelée; je veux également tester que onEditButtonClick est appelé lorsque l'utilisateur clique sur le bouton d'édition défini dans le fichier HTML du composant. Comment puis-je le faire?

24
demandé sur Diego Moreno 2016-10-17 21:13:02

4 réponses

Mon objectif est de vérifier si le 'onEditButtonClick' est invoqué lorsque l'utilisateur clique sur le bouton Modifier et ne vérifie pas seulement la console.journal imprimé.

Vous devrez d'abord configurer le test en utilisant L'angulaire TestBed. De cette façon, vous pouvez réellement saisir le bouton et cliquez dessus. Ce que vous allez faire est de configurer un module, tout comme vous le feriez pour un @NgModule, seulement pour l'environnement de test

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

Ensuite, vous devez espionner la méthode onEditButtonClick, Cliquez sur le bouton, et vérifier que la méthode a été appelée

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

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

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

Ici, nous devons exécuter un test async car le clic sur le bouton contient une gestion asynchrone des événements, et nous devons attendre que l'événement soit traité en appelant fixture.whenStable

Voir Aussi:

41
répondu Paul Samsotha 2018-05-04 12:35:42

Les événements peuvent être testés en utilisant async/fakeAsync fonctions fournies par '@angular/core/testing', puisque tout événement dans le navigateur est asynchrone et poussé vers la boucle/file d'attente d'événements.

Voici un exemple très basique pour tester l'événement click en utilisant fakeAsync.

La fonction fakeAsync permet un style de codage linéaire en exécutant le corps de test dans une zone de test fakeAsync spéciale.

Ici, je teste une méthode appelée par l'événement click.

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

Ci-Dessous est ce que Angulaire docs {[22] } doivent dire:

Le principal avantage de fakeAsync sur async est que le test semble être synchrone. Il n'y a pas de then(...) pour perturber le flux visible de contrôle. Le retour de la promesse fixture.whenStable a disparu, remplacé par tick()

Il y a des limites . Par exemple, vous ne pouvez pas effectuer un appel XHR depuis un fakeAsync

24
répondu Mav55 2017-12-20 00:06:40

J'ai eu un problème similaire (explication détaillée ci-dessous), et je l'ai résolu (dans jasmine-core: 2.52) en utilisant la fonction tick avec la même (ou plus) quantité de millisecondes que dans l'appel d'origine setTimeout.

Par exemple, si j'avais un setTimeout(() => {...}, 2500); (donc il se déclenchera après 2500 ms), j'appellerais tick(2500), et cela résoudrait le problème.

Ce que j'avais dans mon composant, en réaction à un clic de bouton Delete:

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

Elle est mon travailler essai:

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. une excellente explication sur fakeAsync et les asyncs généraux dans les tests peut être trouvée ici: une vidéo sur les stratégies de test avec Angular 2-Julie Ralph, à partir de 8: 10, d'une durée de 4 minutes :)

0
répondu Casper 2018-01-29 11:09:00

Je suis l'aide de Angulaire 6. J'ai suivi la réponse de Mav55 et cela a fonctionné. Cependant, je voulais m'assurer que fixture.detectChanges(); était vraiment nécessaire, alors je l'ai enlevé et cela a toujours fonctionné. Ensuite, j'ai enlevé tick(); pour voir si cela a fonctionné et cela l'a fait. Enfin, j'ai retiré le test de l'emballage fakeAsync(), et surprise, cela a fonctionné.

Donc je me suis retrouvé avec ceci:

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

Et ça a très bien marché.

0
répondu Parziphal 2018-05-28 22:29:20