iT邦幫忙

2022 iThome 鐵人賽

DAY 6
2
Modern Web

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

Day 6 - 單元測試 - 測試 Angular 服務

  • 分享至 

  • xImage
  •  

前言

前幾篇透過 Angular 管道元件大致說明了如何利用 Jasmine 來撰寫測試案例。然而除了管道之外,Angular 應用程式還有元件 (Component)、指令 (Direcitve) 與服務 (Service) 等各種元件類型,這一篇會說服在 Angular 服務中如何撰寫單元測試,來驗證程式的正確性。

範例程式

這一篇會利用範例程式中的 OrderPricesComputeService 服務 (檔名為 services/order-price-compute.service.ts)。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class OrderPriceComputeService {
  constructor() {}

  compute(values: number[]): number {
    const total = values
      .map((value) => value)
      .reduce((acc, value) => acc + value, 0);

    if (total > 5000) {
      return total * 0.8;
    } else if (total > 3000) {
      return total * 0.9;
    } else if (total > 1000) {
      return total * 0.95;
    } else {
      return total;
    }
  }
}

在此此 Angular 服務中程式中,實作了當購物總金額在不同時可以有不同的折扣:

  • 購物總金額超過 5000 時,享有八折折扣
  • 購物總金額超過 3000 時,享有九折折扣
  • 購物總金額超過 1000 時,享有九折五折扣

在單元測試中初始化 Angular 服務

describe('TaiwanDatePipe', () => {
  let pipe: TaiwanDatePipe;

  beforeEach(() => {
    pipe = new TaiwanDatePipe();
  });

  it('當日期為 2022/09/01 時,應回傳 "民國 111 年 9 月 1 日', () => {
    ...
  });

  it('當日期為 1900/09/01 時,應回傳 "民國前 11 年 9 月 1 日', () => {
    ...
  });
});

如上面程式,在先前的範例中,我們利用 new TaiwanDatePipe() 建立初始化管道。這種初始化的方法也是可以使用在 Angular 服務的單元測試上。

describe('OrderPriceComputeService', () => {
  let service: OrderPriceComputeService;

  beforeEach(() => {
    service = new OrderPriceComputeService();
  });

  it('購物總金額為 900 時,不享有任何折扣,付款金額應為 900', () => {
    ...
  });
});

只是現實需求總是沒這麼單純,實際上可能在特定的 Angular 服務中相依於其他 Angular 服務。例如我們希望在 OrderPriceComputeService 執行 compute() 方法時,也呼叫 LogService 服務來記錄相關 Log 資訊(如下程式)。

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class OrderPriceComputeService {
  constructor(private logService: LogService) {}

  compute(values: number[]): number {
    const total = values
      .map((value) => value)
      .reduce((acc, value) => acc + value, 0);

    this.logService.record(`折扣前購物金額為 ${total}`);

    if (total > 5000) {
      return total * 0.8;
    } else if (total > 3000) {
      return total * 0.9;
    } else if (total > 1000) {
      return total * 0.95;
    } else {
      return total;
    }
  }
}

如此一來,我們就必須在初始化 OrderPriceComputeService 服務時,也需要把 LogService 服務實體一併傳入建構式內。

describe('OrderPriceComputeService', () => {
  let service: OrderPriceComputeService;

  beforeEach(() => {
    service = new OrderPriceComputeService(new LogService());
  });

  it('購物總金額為 900 時,不享有任何折扣,付款金額應為 900', () => {
    ...
  });
});

然而,當在 Angular 服務的建構式是注入多個其他服務時,或是所注入的服務還有注入其他服務,都會大大增加測試時初始化服務的複雜度。

利用 TestBed 取得 Angular 服務實體

為了簡化測試時的初始化作業,Angular 提供了 TestBed 讓我們能在測試程式中配置與初始化的環境來模擬 @NgModule,讓我們可以如同 Angular 應用程式一般,透過依賴注入 (Dependency Injection, DI) 的方式來管理元件之間的相依,進一步更容易地去測試依賴於 Angular 框架的行為。

因此,我們就可以修改上面測試程式內容,透過 TestBed 的 inject 方法來取得注入於 Angular 應用程式中的 OrderPriceComputeService 服務實體。

describe('OrderPriceComputeService', () => {
  let service: OrderPriceComputeService;

  beforeEach(() => {
    service = TestBed.inject(OrderPriceComputeService);
  });
});

在 Angular 8 之前,我們會使用 TestBed 的 get 方法取得服務實體,此方法已在 Angular 9 被棄用。

利用 TestBed 管理注入

進一步,如果在 OrderPriceComputeService 服務有注入 LogService 服務時,我們可以利用 TestBed 的 configureTestingModule 方法來設定測試模組的服務提供者,其設定的方式與 @NgModel 相同。

describe('OrderPriceComputeService', () => {
  let service: OrderPriceComputeService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [LogService]
    });
    service = TestBed.inject(OrderPriceComputeService);
  });
});

如此一來就可以透過模擬 @NgModel 的方式,更容易的去測試 Angular 應用程式。

執行測試程式

因此,我們可以利用這幾天所說明的 Jasmine 程式來完成此服務的所有測試情境,並執行 ng test 命令來確認驗證的結果。

https://ithelp.ithome.com.tw/upload/images/20220921/201096456mGxfa8RRI.png

接下來

這一篇利用 TestBed 來模擬 @NgModule,以便於我們測試 Angular 應用程式,完整的測試程式放在 GitHub 中。接下來,就針對當 Angular 服務中使用 HttpClient 來存取後端服務時,其測試程式需要如何撰寫。


上一篇
Day 5 - 單元測試 - 利用 Jasmine 驗證目標程式
下一篇
Day 7 - 單元測試 - 測試使用 HttpClient 的 Angular 服務
系列文
今天我想來在 Angular 應用程式上加上測試保護30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言