【问题标题】:Angular Service Calls API in constructor - Jasmine Test构造函数中的 Angular 服务调用 API - Jasmine 测试
【发布时间】:2019-05-02 19:16:35
【问题描述】:

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

在应用程序负载上实例化单例服务: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.

如果我删除主BeforeEach() 中的httpMock.expectOne(我在该行上有注释,表明试图捕获构造函数)。那么每次测试都会出现这个错误:

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

我 100% 肯定它与构造函数中的 Api 调用有关,据我所知,它创建的每个测试并使用 AppSettings 服务的新实例,这反过来会触发 LoadSettings API 函数。

和在我将调用移至构造函数之前一样,所有测试都通过了。

【问题讨论】:

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


    【解决方案1】:

    经过大量测试后,我发现问题出在我处理从构造函数调用的 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({}),它工作正常。根据我对flush的阅读,定义如下,可以在here找到:

    模拟定时器的异步时间流逝 通过排空宏任务队列直到它为空来伪造异步区域。这 返回值是毫秒的时间 已过。

    我认为发生的事情是在每个测试执行之前发生,flush() 以某种方式将http 请求从队列中取出,从而导致致命问题。在测试期间。或者在冲洗到位的情况下,它阻止了 api 调用的发生。

    【讨论】:

    • 这是一个正确的答案。如果有人对刷新如何导致此问题有更深入的解释,请更新答案。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2019-06-11
    • 2018-09-14
    • 2022-01-24
    • 2017-12-03
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多