iT邦幫忙

2022 iThome 鐵人賽

DAY 10
2
Modern Web

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

Day 10 - 單元測試 - 測試 Angular 元件 - 建立相依的假服務

  • 分享至 

  • xImage
  •  

前言

上一篇說明了如何撰寫在資料繫結情境的單元測試,這一篇我們把要設定給元件屬性的資料,改成透過 Angular 服務跟後端程式取得,來了解如何撰寫這種情境的測試程式。

範例程式

這一篇會使用 ProductPageComponent 頁面元件,這個元件會把 ProductService 服務所取得產品資料,以產品卡片 (ProductCardComponent) 的方式顯示在頁面上。

這支元件相依了 ProductService 服務與 ProductCardComponent 元件。前者利用 HttpClient 跟後端服務取得資料;後者則使用了 Angular Material 與 ShoppingCartService 。因此,如前一篇所述,需要將這些相依對象加入測試模組的設定內。

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [
      HttpClientModule,
      MatButtonModule,
      MatIconModule,
      MatCardModule,
      MatSnackBarModule,
    ],
    declarations: [ProductPageComponent, ProductCardComponent],
    providers: [ShoppingCartService, ProductService],
  }).compileComponents();

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

自製產品服務的假服務

ProductPageComponent 這個頁面元件,在一開始會利用 ProductService 服務來透過 HttpClient 取得後端服務傳回資料。因為在單元測試中並不會相依任何外部資源,所以在測試上我們必須變更此服務的作業,使得在執行測試時不會使用實際的後端服務。

為此,我們可以自訂一個 ProductSpyService 服務,並在服務內實作 getProducts() 方法,讓此方法直接傳回 3 筆產品資料。

const products = [
  new Product({ id: 1, name: '產品 A', price: 999 }),
  new Product({ id: 2, name: '產品 B', price: 200 }),
  new Product({ id: 3, name: '產品 C', price: 10 }),
];

@Injectable({
  providedIn: 'root',
})
export class ProductSpyService {
  getProducts(): Observable<Product[]> {
    return of(products);
  }
}

接下來,利用 providers 陣列來變更測試模組內的服務。

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [ ... ],
    declarations: [ ... ],
    providers: [
      ShoppingCartService,
      { provide: ProductService, useClass: ProductSpyService },
    ],
  }).compileComponents();
  ...
});

最後,就可以依 ProductSpyServicegetProducts() 方法所傳回的筆數,來驗證頁面元件所顯示的結果。

it('當後端服務回傳 3 筆產品資料, 頁面應顯示 3 個產品卡片', () => {
  // Arrange
  var cards = fixture.debugElement.queryAll(
    By.directive(ProductCardComponent)
  );

  // Act

  // Assert
  expect(cards.length).toBe(3);
});

利用 spyOn 設定特定服務方法的回傳值

如果元件所相依的服務有多個方法,或是依不同的測試情境需要傳回不同值的時候,利用上述方法會需要建立不少的 Spy 服務,而 Jasmine 提供了兩個方式讓我們可以很方便的去建立 Spy 服務。

第一種方法,在 providers 屬性的設定上會使用實際的 ProductService 服務。

beforeEach(async () => {
  await TestBed.configureTestingModule({
    imports: [ ... ],
    declarations: [ ... ],
    providers: [
      ShoppingCartService,
      ProductService,
    ],
  }).compileComponents();
  ...
});

在測試程式中,如下面程式,首先利用 TestBed.inject 方法來取得注入的 ProductService 服務實體,並且利用 spyOn 方法設定該服務的 getProducts 方法的回傳值。

it('當後端服務回傳 3 筆產品資料, 頁面應顯示 3 個產品卡片', () => {
  // Arrange
  const productService = TestBed.inject(ProductService);
  spyOn(productService, 'getProducts').and.returnValue(of(products));

  // Act
  component.ngOnInit();
  fixture.detectChanges();

  // Assert
  var cards = fixture.debugElement.queryAll(
    By.directive(ProductCardComponent)
  );
  expect(cards.length).toBe(3);
});

最後,就可以觸發元件的 ngOnInit() 生命週期事件與變更檢測,進而去驗證頁面上的 ProductCardComponent 元件的個數是否為 3 。

利用 jasmine.createSpyObj 方法建立假服務

第二個方式可以利用 Jasmine 提供的 createSpyObj 方法來建立假服務,此方法會回傳一 jsamine.SpyObj<> 型別物件,因此一開始會宣告 productService 變數。

let productService: jasmine.SpyObj<ProductService>;

其次,會利用 createSpyObj<ProductService> 來建立一 Spy 服務,此方法可以傳入 Spy 服務需要的方法名稱。接下來,就會去設定 getProducts 方法預計的回傳資料,以及使用 useValue 方式來替代 ProductService 服務。

beforeEach(async () => {
  productService = jasmine.createSpyObj<ProductService>(['getProducts']);
  productService.getProducts.and.returnValue(of(products));

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

最後,就可以撰寫與自訂假服務一樣的測試案例。

it('當後端服務回傳 3 筆產品資料, 頁面應顯示 3 個產品卡片', () => {
  // Arrange
  var cards = fixture.debugElement.queryAll(
    By.directive(ProductCardComponent)
  );

  // Act

  // Assert
  expect(cards.length).toBe(3);
});

執行測試程式

文章的最後就來執行 ng test

https://ithelp.ithome.com.tw/upload/images/20220925/20109645V2D4TQ6Rtb.png

接下來

這一篇介紹了三種建立假服務物件的方法,讓我們可以在單元測試不與 HttpClient 相依,完整的測試程式可以參考 GitHub 中。接下來就更進一步的說明在這種假服務物件的情境下,有什麼其他測試的方法可以使用。


上一篇
Day 9 - 單元測試 - 測試 Angular 元件 - 資料繫結測試
下一篇
Day 11 - 單元測試 - 測試 Angular 元件 - Spy 物件
系列文
今天我想來在 Angular 應用程式上加上測試保護30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
json_liang
iT邦研究生 5 級 ‧ 2022-09-25 10:54:23

大推 !

阿壹 iT邦新手 3 級 ‧ 2022-09-25 14:49:58 檢舉

感謝~~

我要留言

立即登入留言