Angular-Testing-Library

jwp

John Peters

Posted on February 20, 2020

Angular-Testing-Library

The Angular-Testing-Library implementation fits into the Karma/Jasmine framework like this:

async function renderCheckbox(checked) {
   let temp = await render(CheckboxComponent, {
      imports: [ReactiveFormsModule]
   });
   let control = {
      GroupName: "This group name test",
      PropertyName: "The Checkbox component PropertyName Test",
      Type: "checkbox",
      CurrentValue: "checked",
      Checked: checked
   };
   temp.fixture.componentInstance.control = control;
   temp.fixture.detectChanges();
   return { temp, control };
}
Enter fullscreen mode Exit fullscreen mode

The key function is "render" from which is an import from this framework.

Assume an Angular Component like this:

Typescript

export class CheckboxComponent implements OnInit {
   @Input("control") control = {
      GroupName: "",
      PropertyName: "",
      Type: "",
      CurrentValue: "",
      Checked: ""
   };
   @Output() emitChange: EventEmitter<any> = new EventEmitter();

   public myFormGroup: FormGroup = new FormGroup({
      checkbox: new FormControl(this.control, [])
   });
   constructor() {}

   ngOnInit() {}
   onCheckBoxChanged() {
      let context = this.myFormGroup.controls.checkbox.value;
      let change = {
         GroupName: this.control.GroupName,
         PropertyName: this.control.PropertyName,
         Type: this.control.Type,
         CurrentValue: context
      };
      this.emitChange.emit(change);
   }
}

Enter fullscreen mode Exit fullscreen mode

HTML

<form [formGroup]="myFormGroup">
   <label>{{ control.PropertyName }}</label>
   <input
      data-testid="checkbox"
      *ngIf="control.Type === 'checkbox'"
      formControlName="checkbox"
      type="checkbox"
      [(ngModel)]="control.Checked"
      (change)="onCheckBoxChanged()"
   />
</form>
Enter fullscreen mode Exit fullscreen mode

Write your Angular-Testing-Library Test

This construct bypasses the default Angular test schematic. It majorly improves the dependency chain manual configuration problem.

import { render} from "@testing-library/angular";
import { ReactiveFormsModule } from "@angular/forms";
import { CheckboxComponent } from "../checkbox/checkbox.component";

//our wrapper for the render function
async function renderCheckbox(checked) {
   let temp = 
await render(CheckboxComponent, {
      imports: [ReactiveFormsModule]
   });
   let control = {
      GroupName: "This group name test",
      PropertyName: "The Checkbox component PropertyName Test",
      Type: "checkbox",
      CurrentValue: "checked",
      Checked: checked
   };
   temp.fixture.componentInstance.control = control;
   temp.fixture.detectChanges();
   return { temp, control };
}

//jasmine tests start here
describe("CheckboxComponent", () => {
   it("should have a PropertyName", async () => {
      let { temp, control } = 

await renderCheckbox(true);
      let label = await temp.findByText(control.PropertyName);
      expect(label.innerText).toContain(control.PropertyName);
   });
   it("should not be checked", async () => {
      let { temp, control } = 

await renderCheckbox(false);
      let cb: any = temp.getByTestId("checkbox");
      expect(cb.checked).toBe(false);
   });
   it("should be checked", async () => {
      let { temp, control } = 

await renderCheckbox(true);
      let cb: any = temp.getByTestId("checkbox");
      expect(cb.checked).toBe(true);
   });
   it("should emit change", async () => {
      let { temp, control } = 

await renderCheckbox(true);
      temp.fixture.componentInstance.emitChange.subscribe(change => {
         expect(change.CurrentValue).toBe(false);
      });
      let checkbox: any = temp.getAllByTestId("checkbox")[0];
      checkbox.click();
      let actual = checkbox.checked;
      let currentContext =
         temp.fixture.componentInstance.myFormGroup.controls.checkbox.value;
      expect(actual).toBe(false);
   });
});

Enter fullscreen mode Exit fullscreen mode

Notes

This function is the key:

async function renderCheckbox(checked) {
   let temp = await render(CheckboxComponent, {
      imports: [ReactiveFormsModule]
   });
   let control = {
      GroupName: "This group name test",
      PropertyName: "The Checkbox component PropertyName Test",
      Type: "checkbox",
      CurrentValue: "checked",
      Checked: checked
   };
   temp.fixture.componentInstance.control = control;
   temp.fixture.detectChanges();
   return { temp, control };
}
Enter fullscreen mode Exit fullscreen mode

Notice that temp is the RenderResult, See this article for what a RenderResult looks liks:

The renderResult.fixture.componentInstance is the Angular Component. Getting addressibility to any HTMLElement in the component is easily done using the attribute as shown in the HTML above; (named data-testid) as shown here:

data-testid="checkbox"

Enter fullscreen mode Exit fullscreen mode

Allowing this statement to get the checkbox. We ensure it's type of any because it allows us to easily test the "checked" property.

let cb: any = temp.getByTestId("checkbox");
  expect(cb.checked).toBe(false);
Enter fullscreen mode Exit fullscreen mode

Note that we can subscribe to the component output as follows:

temp.fixture.componentInstance.emitChange.subscribe(change => {
 (change.CurrentValue).toBe(false);
});
Enter fullscreen mode Exit fullscreen mode

However this subscription only works when we emulate a click event on the checkbox, like this:

 let checkbox: any = temp.getAllByTestId("checkbox")[0];
 checkbox.click();
Enter fullscreen mode Exit fullscreen mode

All in all a nice way to allow something else to do the rendering without worrying about the dependencies. Angular-Testing-Library is a good tool.

JWP2020

💖 💪 🙅 🚩
jwp
John Peters

Posted on February 20, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related