构造函数中的Angular Service Calls API-茉莉花测试

时间:2018-11-30 15:53:10

标签: angular unit-testing jasmine karma-jasmine angular-services

我的服务是一个单例,在其构造函数中,它调用一个函数来进行API调用。初始化服务时,此方法非常有用,因为它降低了调用这些应用程序启动Api所需的复杂性和导入。不仅如此,它还使服务独立,因为它不需要依赖AppComponent来导入和调用Api函数。

在应用程序负载上实例化Singleton服务:app.module.ts

....

export class AppModule {

  constructor(private applicationSettings: ApplicationSettings) {

  }
}

...

服务:application-settings.ts

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

@Injectable({
  providedIn: 'root'
})
export class ApplicationSettings{

  ...

  constructor( private httpClient: HttpClient ){
    this.LoadSettings();
  }

  private LoadSettings() {
    this.httpClient.get<any>(this.settingsApiUrl, { observe: 'response' })
    .pipe(
      retry(3)
    ).subscribe(
      data => this.ApiSuccess(data),
      error => this.ApiError(error)
    );
  }

  ...

}

单元测试:application-settings.spec.ts

import { ApplicationSettings } from './application-settings.service';
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { environment } from '@environments/environment';

let applicationSettings;
let httpMock: HttpTestingController;
let settingsApiUrl = environment.ApplicationSettings;

describe('ApplicationSettings', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ApplicationSettings
      ],
      imports: [
        HttpClientTestingModule
      ]
    });
  });

  beforeEach(() => {
    applicationSettings = TestBed.get(ApplicationSettings);
    httpMock = TestBed.get(HttpTestingController);

    // Attempt to catch from constructor
    httpMock.expectOne({
      url: settingsApiUrl,
      method: 'get'
    }).flush({});


    httpMock.verify();
  });

  describe('Tests To Pass', () => {
    fit('should create the app', () => {
      expect(applicationSettings).toBeDefined();
    });

    fit('should be successfull and call ApiSuccess', () => {
      spyOn(applicationSettings, 'ApiSuccess');

      httpMock.expectOne({
        url: settingsApiUrl,
        method: 'get'
      }).flush({});

      applicationSettings.LoadSettings();

      expect(applicationSettings.ApiSuccess).toHaveBeenCalled();
    });

    ...
  });
});

在运行我的测试用例时,有时它们可​​以工作,但是80%的时间中它们将引发错误。一个测试可能抛出1,另一个可能抛出3,另一个可能抛出5(不一定按该顺序:

[object ErrorEvent] thrown
[object ErrorEvent] thrown
[object ErrorEvent] thrown

它会说:

Error: Expected one matching request for criteria "Match method: get, URL: //localhost/api/appSettings", found none.

如果我删除了主httpMock.expectOne中的BeforeEach()(我在那一行上有注释,表示试图抓住构造函数)。然后每个测试都会通过此错误:

Expected no open requests, found 1: GET //localhost/...

就我所知,它创建和使用AppSettings服务的新实例的每个测试都与构造函数中的Api调用有关,这是100%的肯定。

在将调用移到构造函数之前,所有测试都通过了。

1 个答案:

答案 0 :(得分:0)

经过广泛的测试,我发现问题出在我正在处理从构造函数中调用LoadSettings()APi的方式中。正确的实现方式是:

beforeEach(() => {
  applicationSettings = TestBed.get(ApplicationSettings);
  httpMock = TestBed.get(HttpTestingController);

  // Catches the call from the constructor
  httpMock.expectOne({
    url: settingsApiUrl,
    method: 'get'
  });

  httpMock.verify();
});

在此之前,主要概念是正确的,但是我删除了.flush({}),并且工作正常。从我对同花顺的阅读中可以得出以下定义,并且可以找到here

  

为计时器中的计时器模拟异步时间流逝   通过清空宏任务队列直到其为空来创建fakeAsync区域。的   返回值是原本应经过的毫秒数   过去了。

我认为正在发生的事情是每次执行测试之前发生的事情发生之前,flush()正在以某种方式将http请求从队列中移出,从而导致致命的问题。在测试中。或使用冲洗功能可以防止api调用的发生。