[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");
    });
  }));
});
复制代码

 

posted @   Zhentiw  阅读(342)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2019-02-25 [Functional Programming] Working with two functors(Applicative Functors)-- Part1 --.ap
2019-02-25 [React] Preventing extra re-rendering with function component by using React.memo and useCallback
2018-02-25 [ES2018] Two ways to write for-await-of
2016-02-25 [RxJS] Using Observable.create for fine-grained control
点击右上角即可分享
微信分享提示