使用 Karma 和 Jasmine 对具有多个 API 并行调用的服务进行单元测试 / Angular 8

Unit testing for service with multiple API calls in parallel using Karma and Jasmine / Angular 8

我正在尝试为服务和组件编写单元测试(Jasmine 和 Karma),其目的是对星球大战 API 执行两次 API 调用(更多信息在 https://swapi.co/) 与两个不同的 URLS,以获取人员和星舰数据。两个调用都是 return 同时使用 forkJoin.

我的 App 组件调用 ApiService 执行两个调用如下:

应用组件:

import {Component, OnInit} from '@angular/core';
import {ApiService} from '../services/api.service';

export class AppComponent implements OnInit {

  constructor(private apiService: ApiService) {}

onGetData() {
    this.apiService.getData().subscribe((data: any) => {

      this.peopleData = data[0];
      this.starshipData = data[1];

     });

  }

API 服务:

import {HttpClient} from '@angular/common/http';

export class ApiService {
  constructor(private http: HttpClient) {}

  getData(): Observable<any[]> {
    const randomPeoplePage = Math.floor(Math.random() * 9) + 1;
    const peopleUrl = `https://swapi.co/api/people/?page=${randomPeoplePage}`;

    const randomStarshipsPage = Math.floor(Math.random() * 4) + 1;
    const starshipsUrl = `https://swapi.co/api/starships/?page=${randomStarshipsPage}`;

    const peopleProm = this.http.get(peopleUrl);
    const starshipProm = this.http.get(starshipsUrl);

    return forkJoin([peopleProm, starshipProm]);

  }
}

我在测试方面相当缺乏经验,尤其是在如何正确测试 API 调用方面。 在过去的几天里,我一直在尝试不同的解决方案,就像这样:

 it('should return an Observable', () => {
    const randomPeoplePage = Math.floor(Math.random() * 9) + 1;
    const randomStarshipsPage = Math.floor(Math.random() * 4) + 1;


    const dummyData = [
      {
        count: 87,
        next: `https://swapi.co/api/people/?page=${randomPeoplePage + 1}` || null,
        previous: `https://swapi.co/api/people/?page=${randomPeoplePage - 1}` || null,
        results: new Array(87)
      },
      {
        count: 37,
        next: `https://swapi.co/api/starships/?page=${randomStarshipsPage + 1}` || null,
        previous: `https://swapi.co/api/starships/?page=${randomStarshipsPage - 1}` || null,
        results: new Array(37)
      }
    ];

    service.getData().subscribe(data => {
      expect(data.length).toBe(2);
      expect(data).toEqual(dummyData);

      const peopleReq = httpMock.expectOne(`https://swapi.co/api/people/?page=${randomPeoplePage}`);
      const starshipReq = httpMock.expectOne(`https://swapi.co/api/starships/?page=${randomStarshipsPage}`);

      forkJoin([peopleReq, starshipReq]).subscribe(() => {
        expect(peopleReq.request.method).toBe('GET');
        expect(starshipReq .request.method).toBe('GET');
      })

      peopleReq.flush(dummyData[0]);
      starshipReq.flush(dummyData[1]);

    });


  });
});

但是这个测试没有通过,我真的不确定为什么或者我应该调查什么。 我是否需要测试 api.service.spec.tsapp.component.spec.ts 中的 API 调用? 我如何模拟return?我应该单独测试它们吗? 这是一个 'academic' 测试,因此欢迎任何有关最佳实践的提示。

非常感谢!

组件 应包含与数据的表示 有关的逻辑。现在,您可以同时拥有 SmartDumb 组件。
对于 Smart 组件,您可以使用提供数据(存储、路由、提供数据的服务),而对于 Dumb 组件,您可以测试 data 是否 正确显示 and/or 如果 @Output 事件发送正确。

一个服务应该包含业务逻辑(api调用,可能存储数据等...),逻辑与数据表示不直接相关。

在这种情况下,我只是为 getData() 提供一些预定义数据,这样我就不会调用 real API .

  it('....', () => {
    const url1 = 'url1';
    const url2 = 'url2';

    const responseMap = {
      url1: { data: 'url1' },
      url2: { data: 'url2' }
    }

    // Assuming this is imported..    
    class ApiService {
      constructor (private http) { }

      getData () {
      }
    }

    const getSpy = jasmine.createSpy('Http.get').and.callFake((arg) => {
      return of(responseMap[arg]);
    });
    const mockHttp = { get: getSpy }
    const apiService = new ApiService(mockHttp);

    spyOn(apiService, 'getData').and.callFake(function () {
      return forkJoin([this.http.get(url1), this.http.get(url2)])
    });

    apiService.getData()
      .subscribe(([r1, r2]) => {
        expect(getSpy).toHaveBeenCalledWith(url1);
        expect(getSpy).toHaveBeenCalledWith(url2);

        expect(r1).toBe(responseMap['url1']);
        expect(r2).toBe(responseMap['url2']);
      })
  });