Any way to test EventEmitter in Angular2?

Unit TestingAngular

Unit Testing Problem Overview


I have a component that uses an EventEmitter and the EventEmitter is used when someone on the page is clicked. Is there any way that I can observe the EventEmitter during a unit test, and use TestComponentBuilder to click the element that triggers the EventEmitter.next() method and see what was sent?

Unit Testing Solutions


Solution 1 - Unit Testing

Your test could be:

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

when your component is:

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

  buttonClick() {
    this.myEventEmitter.emit('hello');
  }
}

Solution 2 - Unit Testing

You could use a spy, depends on your style. Here's how you would use a spy easily to see if emit is being fired off...

it('should emit on click', () => {
    spyOn(component.eventEmitter, 'emit');
    component.buttonClick();
    expect(component.eventEmitter.emit).toHaveBeenCalled();
    expect(component.eventEmitter.emit).toHaveBeenCalledWith('bar');
});

Solution 3 - Unit Testing

Although the highest voted answers work, they are not demonstrating good testing practices, so I thought I would expand on Günter's answer with some practical examples.

Let's imagine we have the following simple component:

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

The component is the system under test, spying on parts of it breaks encapsulation. Angular component tests should only know about three things:

  • The DOM (accessed via e.g. fixture.nativeElement.querySelector);
  • Names of the @Inputs and @Outputs; and
  • Collaborating services (injected via the DI system).

Anything that involves directly invoking methods on the instance or spying on parts of the component is too closely coupled to the implementation, and will add friction to refactoring - test doubles should only be used for the collaborators. In this case, as we have no collaborators, we shouldn't need any mocks, spies or other test doubles.


One way to test this is by subscribing directly to the emitter, then invoking the click action (see Component with inputs and outputs):

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

Although this interacts directly with the component instance, the name of the @Output is part of the public API, so it's not too tightly coupled.


Alternatively, you can create a simple test host (see Component inside a test host) and actually mount your component:

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

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

then test the component in context:

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

The componentInstance here is the test host, so we can be confident we're not overly coupled to the component we're actually testing.

Solution 4 - Unit Testing

You can subscribe to the emitter or bind to it, if it is an @Output(), in the parent template and check in the parent component if the binding was updated. You can also dispatch a click event and then the subscription should fire.

Solution 5 - Unit Testing

I had a requirement to test the length of the emitted array. So this is how I did this on top of other Answers.

expect(component.myEmitter.emit).toHaveBeenCalledWith([anything(), anything()]);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiontallkid24View Question on Stackoverflow
Solution 1 - Unit TestingcexbrayatView Answer on Stackoverflow
Solution 2 - Unit TestingJoshua Michael CalafellView Answer on Stackoverflow
Solution 3 - Unit TestingjonrsharpeView Answer on Stackoverflow
Solution 4 - Unit TestingGünter ZöchbauerView Answer on Stackoverflow
Solution 5 - Unit TestingprabhatojhaView Answer on Stackoverflow