Alguma maneira de testar o EventEmitter no Angular2?

87

Eu tenho um componente que usa um EventEmitter e o EventEmitter é usado quando alguém na página é clicado. Existe alguma maneira de observar o EventEmitter durante um teste de unidade e usar TestComponentBuilder para clicar no elemento que aciona o método EventEmitter.next () e ver o que foi enviado?

tallkid24
fonte
Você pode fornecer um êmbolo que mostra o que você tentou, então posso dar uma olhada para adicionar as peças que faltam.
Günter Zöchbauer

Respostas:

204

Seu teste pode ser:

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

quando seu componente é:

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

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}
Cexbrayat
fonte
1
Se for uma âncora em que estou clicando em vez de um botão, o seletor de consulta seria apenas um botão em vez de? Estou usando algo exatamente como esse componente, mas o 'expect (value) .toBe (' hello ');' nunca é executado. Eu me pergunto se é porque, em vez disso, é uma âncora.
tallkid24 de
Atualizei minha resposta com uma maneira mais limpa de testar, usando um espião em vez de um emissor real, e acho que deve funcionar (é o que eu realmente faço para as amostras em meu ebook).
cexbrayat de
Isso funciona muito bem, obrigado! Eu sou novo no desenvolvimento de front-end, especialmente em testes de unidade. Isso ajuda muito. Eu nem sabia que a função spyOn existia.
tallkid24 de
Como posso testar isso se usar um TestComponent para encapsular MyComponent? Por exemplo html = <my-component (myEventEmitter)="function($event)"></my-component>e no teste eu faço: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos
1
resposta excelente - muito concisa e
direta
48

Você pode usar um espião, depende do seu estilo. Veja como você usaria um espião facilmente para ver se emitestá sendo disparado ...

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
fonte
Eu atualizei a resposta para o uso não desnecessário de async ou fakeAsync, o que pode ser problemático, conforme apontado em comentários anteriores. Essa resposta continua sendo uma boa solução no Angular 9.1.7. Se alguma coisa mudar, por favor, deixe um comentário e eu atualizarei esta resposta. obrigado por todos que comentaram / moderaram.
Joshua Michael Wagoner
Você não deveria ser expecto espião real (resultado da spyOn()ligação)?
Yuri
Eu perdi o "component.buttonClick ()" após o Spyon. Esta solução resolveu meu problema. Muito obrigado!
Pearl
2

Você pode assinar o emissor ou vincular-se a ele, se for um @Output(), no modelo pai e verificar no componente pai se a vinculação foi atualizada. Você também pode enviar um evento de clique e a assinatura deve ser disparada.

Günter Zöchbauer
fonte
Então, se eu gostei de emitter.subscribe (data => {}); como obteria a saída next ()?
tallkid24
Exatamente. Ou o modelo em TestComponenthas <my-component (someEmitter)="value=$event">(onde someEmitteré um @Output()) então a valuepropriedade de TextComponentdeve ser atualizada com o evento enviado.
Günter Zöchbauer
0

Tive a necessidade de testar o comprimento da matriz emitida. Então foi assim que fiz isso em cima de outras respostas.

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

Embora as respostas com maior votação funcionem, elas não estão demonstrando boas práticas de teste, então pensei em expandir a resposta de Günter com alguns exemplos práticos.

Vamos imaginar que temos o seguinte componente simples:

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

O componente é o sistema em teste, espionando partes dele quebras de encapsulamento. Os testes de componentes angulares devem saber apenas sobre três coisas:

  • O DOM (acessado via, por exemplo, fixture.nativeElement.querySelector );
  • Nomes de @Inputs e@Output s; e
  • Serviços de colaboração (injetados via sistema DI).

Qualquer coisa que envolva invocar métodos diretamente na instância ou espionar partes do componente está muito intimamente ligada à implementação e adicionará atrito à refatoração - duplas de teste devem ser usadas apenas para os colaboradores. Neste caso, como não temos colaboradores, devemos não precisa de nenhum simulações, espiões ou outras duplas de teste.


Uma maneira de testar isso é inscrevendo-se diretamente no emissor e, em seguida, invocando a ação de clique (consulte Componente com entradas e saídas ):

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

Embora isso interaja diretamente com a instância do componente, o nome do @Output faz parte da API pública, portanto, não é muito acoplado.


Como alternativa, você pode criar um host de teste simples (consulte Componente dentro de um host de teste ) e realmente montar seu componente:

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

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

em seguida, teste o componente no contexto:

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

A componentInstanceaqui é o anfitrião de teste , para que possamos ter a certeza de que não estamos excessivamente acoplado ao componente estamos testando realmente.

Jonrsharpe
fonte