Un moyen de tester EventEmitter dans Angular2?

87

J'ai un composant qui utilise un EventEmitter et l'EventEmitter est utilisé lorsque quelqu'un sur la page est cliqué. Est-il possible d'observer l'EventEmitter pendant un test unitaire et d'utiliser TestComponentBuilder pour cliquer sur l'élément qui déclenche la méthode EventEmitter.next () et voir ce qui a été envoyé?

tallkid24
la source
Pouvez-vous fournir un plunker qui montre ce que vous avez essayé, alors je peux jeter un œil pour ajouter les pièces manquantes.
Günter Zöchbauer

Réponses:

204

Votre test pourrait être:

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

lorsque votre composant est:

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

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}
cexbrayat
la source
1
Si c'est une ancre sur laquelle je clique au lieu d'un bouton, le sélecteur de requête serait-il simplement un bouton au lieu d'un bouton? J'utilise quelque chose d'exactement comme ce composant, mais le 'expect (value) .toBe (' hello ');' ne se fait jamais courir. Je me demande si c'est parce que c'est plutôt une ancre.
tallkid24
J'ai mis à jour ma réponse avec une manière plus propre de tester, en utilisant un espion au lieu d'un véritable émetteur, et je pense que cela devrait fonctionner (c'est ce que je fais réellement pour les échantillons de mon ebook).
cexbrayat
Cela fonctionne très bien merci! Je suis nouveau dans le développement front-end, en particulier le test unitaire. Cela aide beaucoup. Je ne savais même pas que la fonction espion existait.
tallkid24
Comment puis-je tester cela si j'utilise un TestComponent pour envelopper MyComponent? Par exemple html = <my-component (myEventEmitter)="function($event)"></my-component>et dans le test que je fais: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos
1
superbe réponse - très concise et
précise
48

Vous pouvez utiliser un espion, cela dépend de votre style. Voici comment utiliser facilement un espion pour voir s'il emitest renvoyé ...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});
Joshua Michael Wagoner
la source
J'ai mis à jour la réponse pour ne pas utiliser inutilement async ou fakeAsync, ce qui peut être problématique, comme indiqué dans les commentaires précédents. Cette réponse reste une bonne solution à partir de Angular 9.1.7. Si quelque chose change, veuillez laisser un commentaire et je mettrai à jour cette réponse. merci pour tous ceux qui ont commenté / modéré.
Joshua Michael Wagoner le
Ne devriez-vous pas expectl'espion réel (résultat de l' spyOn()appel)?
Yuri
J'ai raté le "component.buttonClick ()" après le Spyon. Cette solution a résolu mon problème. Merci beaucoup!
Pearl
2

Vous pouvez vous abonner à l'émetteur ou vous y lier, s'il s'agit d'un @Output(), dans le modèle parent et vérifier dans le composant parent si la liaison a été mise à jour. Vous pouvez également envoyer un événement de clic, puis l'abonnement devrait se déclencher.

Günter Zöchbauer
la source
Donc si j'ai aimé emitter.subscribe (data => {}); comment obtenir la sortie next ()?
tallkid24
Exactement. Ou le modèle dans TestComponenthas <my-component (someEmitter)="value=$event">(où someEmitterest un @Output()), alors la valuepropriété de TextComponentdoit être mise à jour avec l'événement envoyé.
Günter Zöchbauer
0

J'avais besoin de tester la longueur du tableau émis. C'est ainsi que j'ai fait cela en plus d'autres réponses.

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);
prabhatojha
la source
-2

Bien que les réponses aux votes les plus élevés fonctionnent, elles ne démontrent pas de bonnes pratiques de test, j'ai donc pensé développer la réponse de Günter avec quelques exemples pratiques.

Imaginons que nous ayons le composant simple suivant:

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

Le composant est le système testé, espionner certaines de ses parties casse l'encapsulation. Les tests de composants angulaires ne devraient connaître que trois choses:

  • Le DOM (accessible via par exemple fixture.nativeElement.querySelector );
  • Noms des @Inputs et@Output s; et
  • Services collaboratifs (injectés via le système DI).

Tout ce qui implique l'invocation directe de méthodes sur l'instance ou l'espionnage de parties du composant est trop étroitement lié à l'implémentation, et ajoutera du frottement au refactoring - les doubles de test ne devraient être utilisés que pour les collaborateurs. Dans ce cas, comme nous n'avons pas de collaborateurs, nous ne devrions pas avoir besoin de simulacres, d'espions ou d'autres doublons de test.


Une façon de tester ceci est de s'abonner directement à l'émetteur, puis d'appeler l'action de clic (voir Composant avec entrées et sorties ):

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

Bien que cela interagisse directement avec l'instance de composant, le nom de @Outputfait partie de l'API publique, il n'est donc pas trop étroitement couplé.


Vous pouvez également créer un hôte de test simple (voir Composant dans un hôte de test ) et monter réellement votre composant:

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

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

puis testez le composant en contexte:

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

Le componentInstanceici est l' hôte de test , donc nous pouvons être sûrs que nous ne sommes pas trop Coupled pour la composante nous fait tester.

Jonrsharpe
la source