[Unit testing Angular] Complete component code

The component we test against:

import {Component, OnInit} from '@angular/core';
import {Course} from "../model/course";
import {Observable} from "rxjs";
import {CoursesService} from "../services/courses.service";
import {map} from "rxjs/operators";
import {sortCoursesBySeqNo} from './sort-course-by-seq';

@Component({
    selector: 'home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

    beginnerCourses$: Observable<Course[]>;

    advancedCourses$: Observable<Course[]>;

    constructor(private coursesService: CoursesService) {

    }

    ngOnInit() {

      this.reloadCourses();

    }


    reloadCourses() {

      const courses$ = this.coursesService.findAllCourses();

      this.beginnerCourses$ = this.filterByCategory(courses$, 'BEGINNER');

      this.advancedCourses$ = this.filterByCategory(courses$, 'ADVANCED');

    }

    filterByCategory(courses$: Observable<Course[]>, category:string) {
      return courses$.pipe(
        map(courses => courses.filter(course => course.category === category).sort(sortCoursesBySeqNo) )
      );
    }

}

Template:

<div class="container">
  <h3>All Courses</h3>

  <mat-tab-group>
    <ng-container *ngIf="beginnerCourses$ | async as beginnerCourses">
      <mat-tab label="Beginners" *ngIf="beginnerCourses?.length > 0">
        <courses-card-list
          (courseEdited)="reloadCourses()"
          [courses]="beginnerCourses"
        >
        </courses-card-list>
      </mat-tab>
    </ng-container>

    <ng-container *ngIf="advancedCourses$ | async as advancedCourses">
      <mat-tab label="Advanced" *ngIf="advancedCourses?.length > 0">
        <courses-card-list
          (courseEdited)="reloadCourses()"
          [courses]="advancedCourses"
        >
        </courses-card-list>
      </mat-tab>
    </ng-container>
  </mat-tab-group>
</div>

Testing code:

import {
  async,
  ComponentFixture,
  fakeAsync,
  flush,
  flushMicrotasks,
  TestBed,
  tick
} from "@angular/core/testing";
import { CoursesModule } from "../courses.module";
import { DebugElement } from "@angular/core";

import { HomeComponent } from "./home.component";
import { CoursesService } from "../services/courses.service";
import { setupCourses } from "../common/setup-test-data";
import { By } from "@angular/platform-browser";
import { of } from "rxjs";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { click, textContent } from "../common/test-utils";

describe("HomeComponent", () => {
  let fixture: ComponentFixture<HomeComponent>,
    component: HomeComponent,
    el: DebugElement,
    coursesService: any; // TestBed.inject(CoursesService) will fix type issue

  const beginnerCourses = setupCourses().filter(
    cs => cs.category === "BEGINNER"
  );
  const advancedCourses = setupCourses().filter(
    cs => cs.category === "ADVANCED"
  );

  beforeEach(async(() => {
    const coursesServiceSpy = jasmine.createSpyObj("CoursesService", [
      "findAllCourses"
    ]);

    TestBed.configureTestingModule({
      imports: [CoursesModule, NoopAnimationsModule],
      providers: [{ provide: CoursesService, useValue: coursesServiceSpy }]
    })
      .compileComponents()
      .then(() => {
        fixture = TestBed.createComponent(HomeComponent);
        component = fixture.componentInstance;
        el = fixture.debugElement;
        coursesService = TestBed.get(CoursesService);
      });
  }));

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

  it("should display only beginner courses", () => {
    coursesService.findAllCourses.and.returnValue(of(beginnerCourses));
    fixture.detectChanges();

    // only one tab available
    const tabs = el.queryAll(By.css(".mat-tab-label"));
    expect(tabs.length).toEqual(1, "Unexpect number of tabs");
  });

  it("should display only advanced courses", () => {
    coursesService.findAllCourses.and.returnValue(of(advancedCourses));
    fixture.detectChanges();

    const tabs = el.queryAll(By.css(".mat-tab-label"));
    expect(tabs.length).toEqual(1, "Unexpect number of tabs");
  });

  it("should display both tabs", () => {
    coursesService.findAllCourses.and.returnValue(of(setupCourses()));
    fixture.detectChanges();

    const tabs = el.queryAll(By.css(".mat-tab-label"));
    expect(tabs.length).toEqual(2, "Unexpect number of tabs");
  });

  /**
   *
   * async vs fakeAsync
   *
   * Depends on whether you need to call real http, if yes, then async
   * Otherwise, always fakeAsync
   */
  it("should display advanced courses when tab clicked - fakeAsync", fakeAsync(() => {
    coursesService.findAllCourses.and.returnValue(of(setupCourses()));
    fixture.detectChanges();
    const tabs = el.queryAll(By.css(".mat-tab-label"));
    click(tabs[1]);
    fixture.detectChanges();
    flush();
    const cardTitles = el.queryAll(By.css(".mat-card-title"));
    expect(cardTitles.length).toBeGreaterThan(0, "Could not find card titles");
    expect(textContent(cardTitles[0])).toContain("Angular Security Course");
  }));

  it("should display advanced courses when tab clicked - async", async(() => {
    coursesService.findAllCourses.and.returnValue(of(setupCourses()));
    fixture.detectChanges();
    const tabs = el.queryAll(By.css(".mat-tab-label"));
    click(tabs[1]);
    fixture.detectChanges();
    // async will tigger whenStable callback
    fixture.whenStable().then(() => {
      const cardTitles = el.queryAll(By.css(".mat-card-title"));
      expect(cardTitles.length).toBeGreaterThan(
        0,
        "Could not find card titles"
      );
      expect(textContent(cardTitles[0])).toContain("Angular Security Course");
    });
  }));
});
原文地址:https://www.cnblogs.com/Answer1215/p/12363450.html