Есть ли способ протестировать EventEmitter в Angular2?

87

У меня есть компонент, который использует EventEmitter, а EventEmitter используется, когда кто-то щелкает на странице. Есть ли способ, которым я могу наблюдать за EventEmitter во время модульного теста и использовать TestComponentBuilder, чтобы щелкнуть элемент, который запускает метод EventEmitter.next (), и посмотреть, что было отправлено?

Tallkid24
источник
Не могли бы вы предоставить плункер, который показывает, что вы пробовали, тогда я могу посмотреть, чтобы добавить недостающие части.
Günter Zöchbauer

Ответы:

205

Ваш тест может быть:

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

когда ваш компонент:

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

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}
Cexbrayat
источник
1
Если я нажимаю вместо кнопки привязку, будет ли селектор запроса просто кнопкой вместо кнопки? Я использую что-то в точности похожее на этот компонент, но 'expect (value) .toBe (' hello ');' никогда не запускается. Интересно, не потому ли, что это якорь?
tallkid24
Я обновил свой ответ более чистым способом тестирования, используя шпион вместо реального эмиттера, и я думаю, что он должен работать (это то, что я на самом деле делаю для образцов в моей электронной книге).
cexbrayat
Это отлично работает, спасибо! Я новичок в разработке интерфейса, особенно в модульном тестировании. Это очень помогает. Я даже не знал, что существует функция spyOn.
tallkid24
Как я могу это проверить, если использовать TestComponent для обертывания MyComponent? Например, html =, <my-component (myEventEmitter)="function($event)"></my-component>а в тесте я использую: tcb.overrideTemplate (TestComponent, html) .createAsync (TestComponent)
bekos
1
превосходный ответ - очень краткий и по существу - очень полезный общий шаблон
danday74
48

Вы можете использовать шпиона, в зависимости от вашего стиля. Вот как можно легко использовать шпиона, чтобы узнать emit, стреляют ли его ...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});
Джошуа Майкл Ваггонер
источник
Я обновил ответ, чтобы избежать ненужного использования async или fakeAsync, что может быть проблематичным, как указано в предыдущих комментариях. Этот ответ остается хорошим решением для Angular 9.1.7. Если что-то изменится, оставьте комментарий, и я обновлю этот ответ. спасибо всем, кто комментировал / модерировал.
Джошуа Майкл Ваггонер,
Разве вы не должны быть expectнастоящим шпионом (результатом spyOn()звонка)?
Юрий
Я пропустил "component.buttonClick ()" после Spyon. Это решение решило мою проблему. Большое спасибо!
Перл
2

Вы можете подписаться на эмиттер или выполнить привязку к нему, если он есть @Output(), в родительском шаблоне и проверить в родительском компоненте, была ли обновлена ​​привязка. Вы также можете отправить событие щелчка, и тогда подписка должна сработать.

Гюнтер Цохбауэр
источник
Итак, если бы мне понравилось emitter.subscribe (data => {}); как мне получить следующий результат ()?
tallkid24
В яблочко. Или шаблон в TestComponenthas <my-component (someEmitter)="value=$event">(где someEmitterесть @Output()), тогда valueсвойство TextComponentдолжно быть обновлено отправленным событием.
Günter Zöchbauer
0

У меня было требование проверить длину испускаемого массива. Вот как я сделал это поверх других ответов.

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);
прабхатоджа
источник
-2

Хотя ответы с наибольшим количеством голосов работают, они не демонстрируют хорошие методы тестирования, поэтому я подумал, что могу расширить ответ Гюнтера некоторыми практическими примерами.

Представим, что у нас есть следующий простой компонент:

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

Компонент - это тестируемая система, наблюдение за ее частями нарушает инкапсуляцию. Тесты компонентов Angular должны знать только три вещи:

  • DOM (доступ, например, через fixture.nativeElement.querySelector);
  • Имена @Inputs и @Outputs; а также
  • Сотрудничающие службы (введенные через систему DI).

Все, что связано с прямым вызовом методов в экземпляре или слежкой за частями компонента, слишком тесно связано с реализацией и добавит трение к рефакторингу - тестовые двойники должны использоваться только для соавторов. В этом случае, поскольку у нас нет соавторов, нам не нужны никакие моки, шпионы или другие тестовые двойники.


Один из способов проверить это - подписаться непосредственно на эмиттер, а затем вызвать действие щелчка (см. Компонент с входами и выходами ):

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

Хотя это напрямую взаимодействует с экземпляром компонента, имя @Outputявляется частью общедоступного API, поэтому оно не слишком тесно связано.


В качестве альтернативы вы можете создать простой тестовый хост (см. Компонент внутри тестового хоста ) и фактически смонтировать свой компонент:

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

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

затем протестируйте компонент в контексте:

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

componentInstanceВот тест хозяин , так что мы можем быть уверены в том, что мы не слишком связаны с компонентом , мы на самом деле тестирования.

Джонршарп
источник