Categories: Angular

Test parent and child components when passing data with input binding

Angular supports the decomposition of components by allowing you to include one into another. To test a component that contains other elements you have to declare them in the test configuration. Don’t forget about any @Input property of an aggregated component or running the test suite will result with the following error:

Template parse errors: Can't bind to property since it isn't a known property of component

The components

Let’s use the following two components from the official Angular documentation.

The first one is the HeroChildComponent that receives a hero instance from the parent component and displays it in a header:

// component-interaction/src/app/hero-child.component.ts
import { Component, Input } from '@angular/core';
 
import { Hero } from './hero';
 
@Component({
  selector: 'app-hero-child',
  template: `
    <h3>{{hero.name}} says:</h3>
  `
})
export class HeroChildComponent {
  @Input() hero: Hero;
}

The second one is the HeroParentComponent that passes the hero instance to the <app-hero-child> element:

// component-interaction/src/app/hero-parent.component.ts
import { Component } from '@angular/core';
 
import { HEROES } from './hero';
 
@Component({
  selector: 'app-hero-parent',
  template: `
    <h2>{{master}} controls {{heroes.length}} heroes</h2>
    <app-hero-child *ngFor="let hero of heroes"
      [hero]="hero"
    </app-hero-child>
  `
})
export class HeroParentComponent {
  heroes = HEROES;
}

Test the parent component with input binding

The test for the HeroParentComponent will succeed if we declare in it the child component – including the property that is passed with @Input:

// component-interaction/src/app/hero-parent.component.spec.ts
…
import { Component, Input} from '@angular/core';
import { Hero } from './hero';

@Component({selector: 'app-hero-child', template: ''})
class HeroChildComponent {
  @Input() hero: Hero;
}
…
      declarations: [
        HeroParentComponent,
        HeroChildComponent
      ]
…

The following file contains the full test file for HeroParentComponent:

// component-interaction/src/app/hero-parent.component.spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, Input} from '@angular/core';
import { HeroParentComponent } from './app-hero-parent.component';
import { Hero } from './hero';

@Component({selector: 'app-hero-child', template: ''})
class HeroChildComponent {
  @Input() hero: Hero;
}

describe('HeroParentComponent', () => {
  let component: HeroParentComponent;
  let fixture: ComponentFixture<HeroParentComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [
        HeroParentComponent,
        HeroChildComponent
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HeroParentComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Test the child component

Without mocking the hero property in the child component, the test suite fails with the following error:

HeroChildComponent should create
[object ErrorEvent] thrown

You can simply create the mock for hero like this:

// component-interaction/src/app/hero-child.component.spec.ts
…
import { Hero } from './hero';
…
beforeEach(() => {
…
    component.hero = {id:1, name: 'hero1'};
…

The following file contains the full test file for HeroChildComponent:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { HeroChildComponent } from './app-hero-child.component';
import { Hero } from './hero';

describe('HeroChildComponent', () => {
  let component: HeroChildComponent;
  let fixture: ComponentFixture<HeroChildComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ HeroChildComponent ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(HeroChildComponent);
    component = fixture.componentInstance;
    component.hero = {id:1, name: 'hero1'};
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

Photo by Daria Shevtsova on StockSnap

little_pinecone

Share
Published by
little_pinecone

Recent Posts

Simplify the management of user roles in Spring Boot

Spring Security allows us to use role-based control to restrict access to API resources. However,…

3 years ago

Create a custom annotation to configure Spring Boot tests

A custom annotation in Spring Boot tests is an easy and flexible way to provide…

3 years ago

Keycloak with Spring Boot #4 – Simple guide for roles and authorities

Delegating user management to Keycloak allows us to better focus on meeting the business needs…

3 years ago

Keycloak with Spring Boot #3 – How to authorize requests in Swagger UI

Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…

3 years ago

Keycloak with Spring Boot #2 – Spring Security instead of Keycloak in tests

Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can…

3 years ago

Keycloak with Spring Boot #1 – Configure Spring Security with Keycloak

Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…

3 years ago