iT邦幫忙

2022 iThome 鐵人賽

DAY 7
1
Modern Web

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

Day 7 - 單元測試 - 測試使用 HttpClient 的 Angular 服務

  • 分享至 

  • xImage
  •  

前言

上一篇說明了如何撰寫 Angular 服務的測試程式。在實務上,我們常會在 Angular 服務中,利用 HttpClient 來請求後端服務,來取得與處理 Angular 應用程式的資料,這一篇就來看看這種存取後端服務的 Angular 服務要如何撰寫單元測試。

範例程式

這一篇會透過 ProductService 服務 (檔名為 services/product.service.ts) 來說明單元程式的撰寫。

@Injectable({
  providedIn: 'root',
})
export class ProductService {
  private _url = 'http://localhost:3000/products';

  constructor(private httpClient: HttpClient) {}

  getProducts(): Observable<Product[]> {
    return this.httpClient.get<Product[]>(this._url).pipe(
      mergeMap((products) => products),
      map((product) => new Product(product)),
      toArray()
    );
  }
}

安裝 json-server

這支程式會透過 json-server 的 API 來模擬請求後端服務,因此若希望執行此範例程式,需要在 Terminal 執行 npm install -g json-server 命令來安裝 json-server。接著,一樣在 Terminal 執行 json-server src/assets/db.json 命令,就可以透過 GET、POST、PUT 與 DELETE 等 HTTP 方法來存取 db.json 內的資料內容。

使用假的 HttpClient

如同範例程式,在正式的產品程式碼中,我們會在模組中去引用 HttpClientModule 模組,並在Angular 服務的建構式去注入 HttpClient,以便可以使用 HttpClientget 等方法來呼叫後端服務。不過在單元測試並不會與外部資源有任何的依賴性,因此我們必須在單元測試中利用假的 HttpClient 物件來模擬與驗證程式執行的結果。

為此 Angular 提供了 HttpClientTestingModules 來讓我們可以使用模擬的 httpClient 物件,所以我們可以在 TestBed 的設定中去引用此模組,並取得型別為 HttpClientControllerHttpClient 模擬物件,此兩者皆需從 @angular/common/http/testing 匯入。

import {
  HttpClientTestingModule,
  HttpTestingController,
} from '@angular/common/http/testing';

describe('ProductService', () => {
  let service: ProductService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
    });
    service = TestBed.inject(ProductService);
    httpMock = TestBed.inject(HttpTestingController);
  });
});

後端服務請求的測試程式

接下來,因為 getProducts 方法是回傳 Observable<Product> 物件,所以在呼叫此方法時需要被訂閱,否則不會真的執行此方法。而在訂閱方法內,則會去驗證所取得到的結果是否與預期相同,因此測試程式會寫成:

it('應該可以取得產品資料', () => {
  // Arrange

  // Act
  service.getProducts().subscribe((actual) => {
    expect(actual).toEqual([
      new Product({ id: 1, name: '產品 A', price: 999 }),
      new Product({ id: 2, name: '產品 B', price: 200 }),
      new Product({ id: 3, name: '產品 C', price: 10 }),
      new Product({ id: 4, name: '產品 D', price: 150 }),
    ]);
  });

  // Assert
  ...
});

然後,在單元測試中的執行並不會實際去後端取得資料,主要使用 HttpTestingController 物件來模擬 httpClient 物件的行為。因此在驗證上會利用 expectOne 方法,來檢查所請求的 URL 是否被請求過一次,進而驗證所發送的請求是否使用預期的方法。最後利用 flush 方法來決定這個請求要傳回什麼樣的結果,這個結果就會是訂閱方法所得到的結果。

it('應該可以取得產品資料', () => {
  // Arrange

  // Act
  service.getProducts().subscribe((actual) => {
    expect(actual).toEqual([
      new Product({ id: 1, name: '產品 A', price: 999 }),
      new Product({ id: 2, name: '產品 B', price: 200 }),
      new Product({ id: 3, name: '產品 C', price: 10 }),
      new Product({ id: 4, name: '產品 D', price: 150 }),
    ]);
  });

  // Assert
  const testRequest = httpMock.expectOne('http://localhost:3000/products');
  expect(testRequest.request.method).toBe('GET');
  testRequest.flush([
    { id: 1, name: '產品 A', price: 999 },
    { id: 2, name: '產品 B', price: 200 },
    { id: 3, name: '產品 C', price: 10 },
    { id: 4, name: '產品 D', price: 150 },
  ]]);
});

除了驗證請求的 HTTP 方法外,也可以驗證請求的 body,來確認產品程式在對後端服務發送請求時,所傳送的資訊參數是否正確。

expect(testRequest.request.body).toEqual(...);

檢查是否有其他的請求

測試 HTTP 方法之後,還會透過 verify() 方法檢查是否有預期外的請求;因為這個動作會是每個測試 HTTP 的案例完成後執行,所以會在 afterEach 中呼叫這個方法。

afterEach(() => {
  httpMock.verify();
});

執行測試程式

最後在 Terminal 執行 ng test 命令來確認測試程式的驗證結果。

https://ithelp.ithome.com.tw/upload/images/20220922/201096451yD7UQWSsU.png

接下來

這一篇說明了如何測試使用了 HttpClient 的 Angular 服務,完整的測試程式放在 GitHub 中。接下來,就針對在 Angular 應用程式中最常使用的元件 (Component),來看看如何撰寫元件的單元程式。


上一篇
Day 6 - 單元測試 - 測試 Angular 服務
下一篇
Day 8 - 單元測試 - 測試 Angular 元件 - 頁面顯示驗證
系列文
今天我想來在 Angular 應用程式上加上測試保護30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言