共计 4478 个字符,预计需要花费 12 分钟才能阅读完成。
前言
之前的我的项目测试采纳的是应用 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 {} |
总结
正文完