如何用Jest模拟错误触发Observable的catchError管道?
问题
我有一个用于获取数据的服务,代码如下:
@Injectable({ providedIn: 'root' }) export class HeartbeatHttpService { private readonly http = inject(HttpClient); constructor(@Inject('WEBSERVICE_BASE_URL_TOKEN') protected webserviceUrl: string) { } getHeartbeat() { return this.fetchHeartbeat(); } private fetchHeartbeat() { return this.http .get(this.webserviceUrl.concat('/heartbeat')) .pipe(catchError((err) => { console.error('Failed to fetch heartbeat data.'); throw err; })); } }
我已完成该服务获取数据成功场景的单元测试,现需编写失败场景测试以触发上述代码中的catchError管道,但使用Jest无法正确触发。
当前我的测试代码如下:
it('should handle error', () => { const mockError = { message: 'oops! test error' } as Error; // 我尝试过mockImplementation和mockReturnValue结合throwError操作符,但都未触发方法中的catchError jest.spyOn(HttpClient.prototype, 'get').mockImplementation((args) => { return throwError(() => mockError); }); // jest.spyOn(HttpClient.prototype, 'get').mockReturnValue(throwError(() => mockError)); service.getHeartbeat().subscribe({ error: (error) => { console.log(error); } }); });
请问如何正确模拟抛出错误以触发catchError管道?
解决方案
要触发catchError管道,需解决异步流处理和模拟逻辑的问题,具体步骤如下:
- 确保导入
throwError:测试文件中必须从rxjs导入该操作符,否则模拟的Observable无法正确抛出错误。 - 用
fakeAsync+tick处理异步:RxJS Observable是异步执行的,Jest不会自动等待异步操作完成,需要用Angular的fakeAsync包裹测试函数,调用tick()触发流的执行。 - 验证错误处理行为:通过断言
console.error的调用或订阅的错误回调来确认catchError被触发。
修改后的完整测试代码:
import { throwError } from 'rxjs'; import { fakeAsync, tick } from '@angular/core/testing'; import { HttpClient } from '@angular/common/http'; import { HeartbeatHttpService } from './heartbeat-http.service'; describe('HeartbeatHttpService', () => { let service: HeartbeatHttpService; let httpGetSpy: jest.SpyInstance; beforeEach(() => { // 初始化服务并注入mock的基础URL service = new HeartbeatHttpService('http://mock-base-url'); // 创建HttpClient.get方法的Spy httpGetSpy = jest.spyOn(HttpClient.prototype, 'get'); }); it('should trigger catchError when HTTP request fails', fakeAsync(() => { const mockError = new Error('oops! test error'); // 模拟get方法返回错误Observable httpGetSpy.mockReturnValue(throwError(() => mockError)); // 监听console.error的调用 const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); let capturedError: Error | undefined; service.getHeartbeat().subscribe({ error: (err) => { capturedError = err; } }); // 触发Observable的异步执行 tick(); // 断言错误被正确捕获并传递 expect(capturedError).toEqual(mockError); // 断言catchError中的错误日志被输出 expect(consoleErrorSpy).toHaveBeenCalledWith('Failed to fetch heartbeat data.'); // 恢复console.error的原始实现,避免影响其他测试 consoleErrorSpy.mockRestore(); })); });
额外注意事项:
- 更推荐使用Angular官方的
HttpClientTestingModule来提供Mock HttpClient,而非直接spyHttpClient.prototype,这是Angular测试的最佳实践。 - 测试完成后记得清理Spy,防止污染其他测试用例的执行环境。
内容的提问来源于stack exchange,提问作者Cody Pritchard
相关产品推荐
相关产品推荐

