iT邦幫忙

2022 iThome 鐵人賽

DAY 14
3
Modern Web

今天我想來在 Angular 應用程式上加上測試保護系列 第 14

Day 14 - 單元測試 - 測試 Angular 元件 - 路由測試

  • 分享至 

  • xImage
  •  

前言

利用 Angular 框架所開發的是單一頁面應用程式,因而會利用路由來進行不同頁面的切換。這一篇就來說明要如何針對路由的作業撰寫單元測試程式。

範例程式

這一篇會使用到 ProductPageComponentProductDetailPageComponent 這兩個頁面元件。前者用來說明如何撰寫「切換至產品明細頁面路由」測試情境。

onGoto(id: number): void {
  void this.router.navigate(['product', 'detail', id]);
}

而後者則用來撰寫「依路由網址參數取得產品明細資料」的測試情境。

ngOnInit(): void {
  this.product$ = this.route.paramMap.pipe(
    map((paramMap) => paramMap.get('id')!),
    switchMap((id) => this.productService.getProduct(+id))
  );
}

檢查切換至產品明細頁面路由

如一開始的程式,我們利用 router.navigte 來實作路由頁面的切換,因此可以建立 Router 的 Spy 物件,確認是否有呼叫 Router 物件的 navigate() 方法,且傳入預期的參數。

describe('ProductPageComponent', () => {
  let router: jasmine.SpyObj<Router>;

  beforeEach(async () => {
    router = jasmine.createSpyObj<Router>(['navigate']);

    await TestBed.configureTestingModule({
      imports: [ ... ],
      declarations: [ ... ],
      providers: [
        { provide: Router, useValue: router },
        ...
      ],
    }).compileComponents();
  });

  it('當點選 id 為 1 產品的明細按鈕, 應轉址到"product/detail/1"', () => {
    // Arrange
    var cards = fixture.debugElement.queryAll(
      By.directive(ProductCardComponent)
    );
    var detailButton = cards[0].query(By.css('button'));

    // Act
    detailButton.triggerEventHandler('click', null);

    // Assert
    expect(router.navigate).toHaveBeenCalledWith(['product', 'detail', 1]);
  });
});

檢查依路由網址參數取得產品明細資料

在撰寫測試之前,由於 ProductDetailPageComponent 頁面元件會透過 ProductService 服務去取得特定產品編號的資料,因此需要先利用此服務的 Spy 物件來替代原本的服務。

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

  let productService: jasmine.SpyObj<ProductService>;

  beforeEach(async () => {
    productService = jasmine.createSpyObj<ProductService>(['getProduct']);

    await TestBed.configureTestingModule({
      imports: [MatButtonModule],
      declarations: [ProductDetailPageComponent],
      providers: [
        { provide: ProductService, useValue: productService },
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(ProductDetailPageComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });
});

不同於利用 Jasmine 建立 Spy 物件來替代掉本來注入的服務實體,在實作上會透過 ActivatedRoute 內的 paramMap 屬性來取得路由所傳入的值,所以測試上需要利用 Angular 提供的 convertToParamMap() 方法來建立 ParamMap,因此這裡會在 src/testing 資料夾建立由 Angular 官網中所提供的 ActivateRouteSub 類別。

import { convertToParamMap, ParamMap, Params } from '@angular/router';
import { ReplaySubject } from 'rxjs';

export class ActivatedRouteStub {
  private subject = new ReplaySubject<ParamMap>();

  constructor(initialParams?: Params) {
    this.setParamMap(initialParams);
  }

  readonly paramMap = this.subject.asObservable();

  setParamMap(params: Params = {}) {
    this.subject.next(convertToParamMap(params));
  }
}

接著,就可以在測試案例中建立此 Stub 物件,且傳入產品編號,並取代掉 ActivateRoute 物件。

describe('ProductDetailPageComponent', () => {
  ...
 
  let activatedRoute: ActivatedRouteStub;

  beforeEach(async () => {
    activatedRoute = new ActivatedRouteStub({ id: '1' });
    ...

    await TestBed.configureTestingModule({
      imports: [MatButtonModule],
      declarations: [ProductDetailPageComponent],
      providers: [
        { provide: ActivatedRoute, useValue: activatedRoute },
        { provide: ProductService, useValue: productService },
      ],
    }).compileComponents();

    ...
  });
});

如此一來,就可以去驗證頁面載入時,是否有呼叫 ProductServicegetProduct() 方法,並傳入的參數為 1

it('頁面載入時, 應取得 id 為 1 的產品', () => {
  // Arrange

  // Act

  // Assert
  expect(productService.getProduct).toHaveBeenCalledWith(1);
});

執行測試程式

最後就執行 ng test 來確認測試執行的結果。

https://ithelp.ithome.com.tw/upload/images/20220929/201096455A3oLox9kH.png

接下來

這一篇說明了如何撰寫路由相關的單元測試程式,完整的測試程式可以參考 GitHub。接下來,就是元件測試的最後一篇,來說明如何利用 Page 物件來封裝元件的頁面元素,以降低測試程式的複雜度。


上一篇
Day 13 - 單元測試 - 測試 Angular 元件 - 非同步驗證測試
下一篇
Day 15 - 單元測試 - 測試 Angular 元件 - 使用 Page 物件
系列文
今天我想來在 Angular 應用程式上加上測試保護30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言