# Angular @Input and @Output testing

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: https://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 in `div`
    
* when user clicks `button` the value from the `title` field is emitted through `buttonClick` EventEmitter.
    

```typescript
// 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);
  }
}
```

```html
<!-- 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.

```typescript
@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
    

```typescript
declarations: [
  TestComponent,
  DummyComponent,
],
```

* create the fixture and component reference
    

```typescript
fixture = TestBed.createComponent(DummyComponent);
component = fixture.componentInstance;
```

* spy on `buttonClicked` method
    

```typescript
jest.spyOn(component, 'buttonClicked');
```

* test component default settings
    

```typescript
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

```typescript
getButton().nativeElement.click();
```

and validating the wrapper's method call

```typescript
expect(component.buttonClicked).toHaveBeenCalledTimes(1);
expect(component.buttonClicked).toHaveBeenCalledWith('');
```

Test the passed value and output

```typescript
  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

```typescript
component.title = 'my title';
fixture.detectChanges();
```

The rest is similar to the previous test.

Full test:

```typescript
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 { }
}
```

# Source code

https://gitlab.com/barcioch-blog-examples/series-angular-testing-07-component-input-output-testing
