Angular @Input and @Output testing
Table of contents
In this article, I'll show how to test Angular component with inputs and outputs.
Setup
If unsure how to set up Angular with Jest please refer to the article: barcioch.pro/angular-with-jest-setup
Component
Create a component with one input and one output:
the component takes a
title
value and displays it indiv
when user clicks
button
the value from thetitle
field is emitted throughbuttonClick
EventEmitter.
// test.component.ts
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent {
@Input() title = '';
@Output() buttonClick = new EventEmitter<string>();
click(): void {
this.buttonClick.emit(this.title);
}
}
<!-- test.component.html -->
<div class="title">{{ title }}</div>
<button
type="button"
(click)="click()"
class="button">Button</button>
Test
To test a component I'll wrap it in the wrapper
component, so it will work as used in the real application. It will also make it easy to pass the values and validate outputs.
@Component({
selector: 'app-dummy',
template: `<app-test [title]="title" (buttonClick)="buttonClicked($event)"></app-test>`,
styleUrls: ['./test.component.css']
})
export class DummyComponent {
title?: string;
click(): void { }
buttonClicked(value: string): void { }
}
The test works as follows:
- declare all needed components
declarations: [
TestComponent,
DummyComponent,
],
- create the fixture and component reference
fixture = TestBed.createComponent(DummyComponent);
component = fixture.componentInstance;
- spy on
buttonClicked
method
jest.spyOn(component, 'buttonClicked');
- test component default settings
describe('when no title is passed ', () => {
it('should display empty title', () => {
expect(getTitle().nativeElement.innerHTML).toBe('');
});
describe('and user clicks button', () => {
beforeEach(() => {
getButton().nativeElement.click();
fixture.detectChanges();
});
it('should emit empty string', () => {
expect(component.buttonClicked).toHaveBeenCalledTimes(1);
expect(component.buttonClicked).toHaveBeenCalledWith('');
});
});
});
Note that I'm clicking the HTML Button
element
getButton().nativeElement.click();
and validating the wrapper's method call
expect(component.buttonClicked).toHaveBeenCalledTimes(1);
expect(component.buttonClicked).toHaveBeenCalledWith('');
Test the passed value and output
describe('when "my title" value is passed as "title"', () => {
beforeEach(() => {
component.title = 'my title';
fixture.detectChanges();
});
it('should display "my title"', () => {
expect(getTitle().nativeElement.innerHTML).toBe('my title');
});
describe('and user clicks button', () => {
beforeEach(() => {
getButton().nativeElement.click();
fixture.detectChanges();
});
it('should emit empty string', () => {
expect(component.buttonClicked).toHaveBeenCalledTimes(1);
expect(component.buttonClicked).toHaveBeenCalledWith('my title');
});
});
});
Set value directly on the wrapper and run change detection
component.title = 'my title';
fixture.detectChanges();
The rest is similar to the previous test.
Full test:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TestComponent } from './test.component';
import { Component, DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
describe('TestComponent', () => {
let fixture: ComponentFixture<DummyComponent>;
let component: DummyComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TestComponent,
DummyComponent,
],
}).compileComponents();
fixture = TestBed.createComponent(DummyComponent);
component = fixture.componentInstance;
jest.spyOn(component, 'buttonClicked');
});
describe('when no title is passed ', () => {
it('should display empty title', () => {
expect(getTitle().nativeElement.innerHTML).toBe('');
});
describe('and user clicks button', () => {
beforeEach(() => {
getButton().nativeElement.click();
fixture.detectChanges();
});
it('should emit empty string', () => {
expect(component.buttonClicked).toHaveBeenCalledTimes(1);
expect(component.buttonClicked).toHaveBeenCalledWith('');
});
});
});
describe('when "my title" value is passed as "title"', () => {
beforeEach(() => {
component.title = 'my title';
fixture.detectChanges();
});
it('should display "my title"', () => {
expect(getTitle().nativeElement.innerHTML).toBe('my title');
});
describe('and user clicks button', () => {
beforeEach(() => {
getButton().nativeElement.click();
fixture.detectChanges();
});
it('should emit empty string', () => {
expect(component.buttonClicked).toHaveBeenCalledTimes(1);
expect(component.buttonClicked).toHaveBeenCalledWith('my title');
});
});
});
const getTitle = (): DebugElement => {
return fixture.debugElement.query(By.css('.title'));
}
const getButton = (): DebugElement => {
return fixture.debugElement.query(By.css('.button'));
}
});
@Component({
selector: 'app-dummy',
template: `
<app-test [title]="title" (buttonClick)="buttonClicked($event)"></app-test>`,
styleUrls: ['./test.component.css']
})
export class DummyComponent {
title: string = '';
buttonClicked(value: string): void { }
}