前言

之前的我的项目测试采纳的是应用Stub替换原有的组件进行测试,现在的问卷零碎采纳了新的思维,就是应用应用MockHttpClientModule替换HttpClientModule,前后台接口齐全对立,更靠近于实在申请,本文以总结学习心得为主,总结一下该办法的思路。

办法

首先看一下要测试的办法:

/** * 通过试卷ID获取答卷  * 1. 如果存在尚未实现的答卷,则返回尚未实现的答卷  * 2. 如果不存在尚未实现的答卷,则新建一个答卷返回  * @param id 试卷ID */getByPaperId(id: number): Observable<AnswerSheet> {  return this.httpClient.get<AnswerSheet>(`${this.baseUrl}/${id}`);}

测试方法:

beforeEach(() => {  TestBed.configureTestingModule({    imports: [      MockApiModule    ], providers: [      MockHttpClient    ]  }); service = TestBed.inject(AnswerSheetService);});it('测试模仿接口服务是否失效', () => {  expect(service).toBeTruthy(); let called = false; service.getByPaperId(123).subscribe(data => {    expect(data.id).toEqual(123); called = true; }); getTestScheduler().flush(); expect(called).toBeTrue();});

MockHttpClient

export class MockHttpClient {  constructor(private mockApiService: MockApiService) {  }  get<T>(url: string, options?: {    headers?: HttpHeaders | {      [header: string]: string | string[]; }; params?: HttpParams | {      [param: string]: string | string[]; }; }): Observable<T> {    return this.mockApiService.get<T>(url, options); }

MockApiService

get办法

/** * get办法 * @param url 申请地址 * @param options 选项 */  get<T>(url: string, options = {} as {    headers?: HttpHeaders | {      [header: string]: string | string[]; }; params?: HttpParams | {      [param: string]: string | string[]; }; }): Observable<T> {    return this.request<T>('get', url, {      observe: 'response', responseType: 'json', headers: options.headers, params: options.params }); }}

request

/** * 所有的GETPOSTDELETEPUTPATCH办法最终均调用request办法。 * 如果以后request不可能满足需要,则请移步angular官网提供的HttpClient * * 该办法先依据method进行匹配,接着依据URL进行正则表达式的匹配。 * 匹配胜利后将参数传入接口并获取模仿接口的返回值 * * @param method 申请办法 * @param url 申请地址 * @param options 选项 */ request<R>(method: string, url: string, options: {  body?: any; headers?: HttpHeaders | {    [header: string]: string | string[]; }; reportProgress?: boolean; observe: 'response'; params?: HttpParams | {    [param: string]: string | string[]; }; responseType?: 'json'; withCredentials?: boolean;}): Observable<R> {  let result = null as R; let foundCount = 0; const urlRecord = this.routers[method] as Record<string, RequestCallback<any>>; for (const key in urlRecord) {    if (urlRecord.hasOwnProperty(key)) {      const reg = new RegExp(key); if (reg.test(url)) {        const callback = urlRecord[key] as RequestCallback<R>; callback(url.match(reg), options.params, options.headers, (body) => {          result = body; foundCount++; if (foundCount > 1) {            throw Error('匹配到了多个URL信息,请检定注入服务的URL信息,URL信息中存在匹配抵触'); }        }); }    }  }  if (null === result) {    throw Error('未找到对应的模仿返回数据,请查看url、method是否正确,模仿注入服务是否失效'); }  return testingObservable(result);}

registerMockApi

/** * 注册模仿接口 * @param url 申请地址 * @param method 申请办法 * @param callback 回调 */registerMockApi<T>(method: RequestMethodType, url: string, callback: RequestCallback<T>): void {  if (undefined === this.routers[method] || null === this.routers[method]) {    this.routers[method] = {} as Record<string, RequestCallback<T>>; }  if (isNotNullOrUndefined(this.routers[method][url])) {    throw Error(`在地址${url}已存在${method}的路由记录`); }  this.routers[method][url] = callback;}

AnswerSheetApi

registerGetByPaperId()

private baseUrl = '/answerSheet';/** * 注册GetByPaperId接口  * 注册实现后,当其它的服务尝试httpClient时  * 则会按此时注册的办法、URL地址进行匹配  * 匹配胜利后则会调用在此申明的回调函数,同时将申请地址、申请参数、申请header信息传过来  * 咱们最初依据接管的参数返回特定的模仿数据,该数据与后盾的实在接口放弃严格对立 */ registerGetByPaperId(): void {  this.mockHttpService.registerMockApi<AnswerSheet>(    `get`, `^${this.baseUrl}/(d+)$`, (urlMatches, httpParams, httpHeaders, callback) => {      const id = urlMatches[1]; callback({        id: +id      }); }  );}

injectMockHttpService

/** * MOCK服务。  */mockHttpService: MockApiService;  /** * 注入MOCK服务  ** @param mockHttpService 模仿HTTP服务 */injectMockHttpService(mockHttpService: MockApiService): void {  this.mockHttpService = mockHttpService; this.registerGetByPaperId();}

MockApiService

constructor()

/** * 注册模仿接口  * @param clazz 接口类型 */ static registerMockApi(clazz: Type<Api>): void {  this.mockApiRegisters.push(clazz);}/** * 循环调用从而实现所有的接口注册 */ constructor() { MockApiService.mockApiRegisters.forEach(api => { const instance = new api(); instance.injectMockHttpService(this); });}

// AnswerSheetApi
MockApiService.registerMockApi(AnswerSheetApi);

testingObservable

/** * 返回供测试用的观察者  * 如果以后为测试过程中,则调用cold办法返回观察者将不出抛出异样。  * 否则应用of办法返回观察者  * @param data 返回的数据 * @param delayCount 提早返回的工夫距离 */ export function testingObservable<T>(data: T, delayCount = 1): Observable<T> {  try {    let interval = ''; for (let i = 0; i < delayCount; i++) {      interval += '---'; }    return cold(interval + '(x|)', {x: data}); } catch (e) {    if (e.message === 'No test scheduler initialized') {      return of(data).pipe(delay(delayCount * 500)); } else {      throw e; }  }}

MockApiModule

/** * 模仿后盾接口模块  * 因为MockHttpClient依赖于MockApiService  * 所以必须先申明MockApiService,而后再申明MockHttpClient  * 否则将产生依赖异样 * 每减少一个后盾模仿接口,则须要对应增加到providers。  * 否则模仿接口将被angular的摇树优化摇掉,从而使得其注册办法失败  */ @NgModule({  providers: [    MockApiService, {provide: HttpClient, useClass: MockHttpClient}, AnswerSheetApi  ]})export class MockApiModule {}

总结