Rad*_*ari 5 rxjs jestjs angular
我有一个发出HTTP请求的Angular服务.该服务的主要工作是刷新访问令牌并在请求结果为401时重试请求.该服务还能够宽限期地处理多个并发请求:如果有3个请求导致401,则令牌将只刷新一次,所有3个请求将被重播.以下GIF总结了此行为:

我的问题是我似乎无法测试这种行为.最初,我的测试总是因超时而失败,因为我没有调用我的订阅或错误方法.添加fakeAsync之后我再也没有得到超时,但我的观察者仍然没有被调用.我还注意到,如果我从tokenObservable中删除共享运算符,则会调用我的测试中的订阅,但通过这样做,我将失去多播的好处.
这是不能正常工作的测试
it('refreshes token when getting a 401 but gives up after 3 tries', fakeAsync(() => {
const errorObs = new Observable(obs => {
obs.error({ status: 401 });
}).pipe(
tap(data => {
console.log('token refreshed');
})
);
const HttpClientMock = jest.fn<HttpClient>(() => ({
post: jest.fn().mockImplementation(() => {
return errorObs;
})
}));
const httpClient = new HttpClientMock();
const tokenObs = new Observable(obs => {
obs.next({ someProperty: 'someValue' });
obs.complete();
});
const AuthenticationServiceMock = jest.fn<AuthenticationService>(() => ({
refresh: jest.fn().mockImplementation(() => {
return tokenObs;
})
}));
const authenticationService = new AuthenticationServiceMock();
const service = createSut(authenticationService, httpClient);
service.post('controller', {}).subscribe(
data => {
expect(true).toBeFalsy();
},
(error: any) => {
expect(error).toBe('random string that is expected to fail the test, but it does not');
expect(authenticationService.refresh).toHaveBeenCalledTimes(3);
}
);
}));
Run Code Online (Sandbox Code Playgroud)
这就是我在SUT中注入模拟的方法:
const createSut = (
authenticationServiceMock: AuthenticationService,
httpClientMock: HttpClient
): RefreshableHttpService => {
const config = {
endpoint: 'http://localhost:64104',
login: 'token'
};
const authConfig = new AuthConfig();
TestBed.configureTestingModule({
providers: [
{
provide: HTTP_CONFIG,
useValue: config
},
{
provide: AUTH_CONFIG,
useValue: authConfig
},
{
provide: STATIC_HEADERS,
useValue: new DefaultStaticHeaderService()
},
{
provide: AuthenticationService,
useValue: authenticationServiceMock
},
{
provide: HttpClient,
useValue: httpClientMock
},
RefreshableHttpService
]
});
try {
const testbed = getTestBed();
return testbed.get(RefreshableHttpService);
} catch (e) {
console.error(e);
}
};
Run Code Online (Sandbox Code Playgroud)
以下是被测系统的相关代码:
@Injectable()
export class RefreshableHttpService extends HttpService {
private tokenObservable = defer(() => this.authenthicationService.refresh()).pipe(share());
constructor(
http: HttpClient,
private authenthicationService: AuthenticationService,
injector: Injector
) {
super(http, injector);
}
public post<T extends Response | boolean | string | Array<T> | Object>(
url: string,
body: any,
options?: {
type?: { new (): Response };
overrideEndpoint?: string;
headers?: { [header: string]: string | string[] };
params?: HttpParams | { [param: string]: string | string[] };
}
): Observable<T> {
return defer<T>(() => {
return super.post<T>(url, body, options);
}).pipe(
retryWhen((error: Observable<any>) => {
return this.refresh(error);
})
);
}
private refresh(obs: Observable<ErrorResponse>): Observable<any> {
return obs.pipe(
mergeMap((x: ErrorResponse) => {
if (x.status === 401) {
return of(x);
}
return throwError(x);
}),
mergeScan((acc, value) => {
const cur = acc + 1;
if (cur === 4) {
return throwError(value);
}
return of(cur);
}, 0),
mergeMap(c => {
if (c === 4) {
return throwError('Retried too many times');
}
return this.tokenObservable;
})
);
}
}
Run Code Online (Sandbox Code Playgroud)
它继承的类:
@Injectable()
export class HttpService {
protected httpConfig: HttpConfig;
private staticHeaderService: StaticHeaderService;
constructor(protected http: HttpClient, private injector: Injector) {
this.httpConfig = this.injector.get(HTTP_CONFIG);
this.staticHeaderService = this.injector.get(STATIC_HEADERS);
}
Run Code Online (Sandbox Code Playgroud)
由于某些未知原因,它不会在第二次调用时解析refresh方法返回的observable.奇怪的是,如果从SUT中删除tokenObservable属性中的share运算符,它就可以工作.它可能需要做一些时间安排.与Jasmine不同,Jest并不会模仿RxJs使用的Date.now.一种可能的方法是尝试使用来自RxJs的VirtualTimeScheduler来模拟时间,尽管这就是fakeAsync应该做的事情.
依赖关系和版本:
以下文章帮助我实现了功能: RxJS:了解发布和共享运算符
我已经研究过这个,似乎我有一些想法为什么它不适合你:
\n\n1) Angular HttpClient 服务在异步代码中抛出错误,但您是同步执行的。结果它破坏了共享运算符。如果您可以调试,您可以通过查看来发现问题ConnectableObservable.ts
在您的测试中,连接仍将保持打开状态,而 HttpClient 异步代码中的连接取消订阅并关闭,以便下次创建新连接。
\n\n要修复它,您还可以在异步代码中触发 401 错误:
\n\nconst errorObs = new Observable(obs => {\n setTimeout(() => {\n obs.error({ status: 404 });\n });\n...\nRun Code Online (Sandbox Code Playgroud)\n\n但你必须等待所有异步代码都已执行完毕tick:
service.post(\'controller\', {}).subscribe(\n data => {\n expect(true).toBeFalsy();\n },\n (error: any) => {\n expect(error).toBe(\'Retried too many times\');\n expect(authenticationService.refresh).toHaveBeenCalledTimes(3);\n }\n);\n\ntick(); // <=== add this\nRun Code Online (Sandbox Code Playgroud)\n\n2)你应该删除你的以下表达式RefreshableHttpService:
mergeScan((acc, value) => {\n const cur = acc + 1;\n if (cur === 4) { <== this one\n return throwError(value);\n }\nRun Code Online (Sandbox Code Playgroud)\n\n因为我们不想抛出错误value上下文错误。
之后您应该捕获所有刷新调用。
\n\n我还创建了示例项目https://github.com/alexzuza/angular-cli-jest
\n\n只要尝试npm i一下npm t。
Share operator causes Jest test to fail\n \xe2\x88\x9a refreshes token when getting a 401 but gives up after 3 tries (41ms)\n\n console.log src/app/sub/service.spec.ts:34\n refreshing...\n\n console.log src/app/sub/service.spec.ts:34\n refreshing...\n\n console.log src/app/sub/service.spec.ts:34\n refreshing...\n\nTest Suites: 1 passed, 1 total\nTests: 1 passed, 1 total\nSnapshots: 0 total\nTime: 4.531s, estimated 5s\nRan all test suites.\nRun Code Online (Sandbox Code Playgroud)\n\n也可以通过调试npm run debug
| 归档时间: |
|
| 查看次数: |
107 次 |
| 最近记录: |