You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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管道,需解决异步流处理和模拟逻辑的问题,具体步骤如下:

  1. 确保导入throwError:测试文件中必须从rxjs导入该操作符,否则模拟的Observable无法正确抛出错误。
  2. 用fakeAsync+tick处理异步:RxJS Observable是异步执行的,Jest不会自动等待异步操作完成,需要用Angular的fakeAsync包裹测试函数,调用tick()触发流的执行。
  3. 验证错误处理行为:通过断言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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 01:40:17