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

在Angular中如何使用Jest对包含observables的服务进行单元测试

基于Jest的带Observables的Angular服务单元测试实现方案

以下为通用可落地的实现步骤与测试示例,可直接适配你自己的待测试服务:

前置环境准备

首先确保你的Angular项目已配置好Jest测试环境,测试前需要导入的核心依赖包括:

  • Angular测试工具包的TestBed、可选的fakeAsync/tick等异步测试工具
  • RxJS的of、throwError、firstValueFrom(RxJS 7+)、take等工具与操作符
  • 待测试服务的所有依赖(如HttpClient、其他依赖服务)需要提前做Mock,不需要引入真实实现

基础测试文件结构

import { TestBed } from '@angular/core/testing';
import { HttpClient } from '@angular/common/http';
import { ServiceToBeTested } from './service-to-be-tested.service';
import { of, throwError, firstValueFrom } from 'rxjs';

describe('ServiceToBeTested', () => {
  let service: ServiceToBeTested;
  // Mock所有依赖的服务,避免真实请求或外部依赖影响测试结果
  const httpClientMock = {
    get: jest.fn(),
    post: jest.fn()
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ServiceToBeTested,
        // 用Mock对象替换真实的依赖服务
        { provide: HttpClient, useValue: httpClientMock }
      ]
    });
    service = TestBed.inject(ServiceToBeTested);
    // 每次测试前清空所有mock的调用记录
    jest.clearAllMocks();
  });
});

核心测试场景实现

场景1:测试返回冷Observable的业务方法(如HTTP请求封装)

这类是最常见的场景,比如服务中封装的后端接口请求方法,返回单次发射值的冷Observable,两种常用测试写法:

  • 写法1:subscribe + done 回调,逻辑直观易懂
it('接口请求成功时应返回预期数据', (done) => {
  const mockResponse = { code: 200, data: [{ id: 1, name: '测试数据' }] };
  // 给mock的依赖方法指定返回值为正常的Observable
  httpClientMock.get.mockReturnValue(of(mockResponse));

  service.getList().subscribe({
    next: (res) => {
      // 断言返回结果符合预期
      expect(res).toEqual(mockResponse.data);
      // 断言依赖方法被正确调用
      expect(httpClientMock.get).toHaveBeenCalledWith('/api/list');
      done();
    },
    error: done.fail // 异常时直接标记测试失败,避免测试超时
  });
});
  • 写法2:async/await + firstValueFrom,代码更简洁
    注:firstValueFrom 为RxJS 7+提供的方法,低版本RxJS可替换为.toPromise()
it('接口请求成功时应返回预期数据', async () => {
  const mockResponse = { code: 200, data: [{ id: 1, name: '测试数据' }] };
  httpClientMock.get.mockReturnValue(of(mockResponse));

  const res = await firstValueFrom(service.getList());
  expect(res).toEqual(mockResponse.data);
  expect(httpClientMock.get).toHaveBeenCalledWith('/api/list');
});

场景2:测试Observable抛出错误的分支逻辑

验证服务在请求失败、业务异常等场景下的处理逻辑是否正确:

it('接口请求失败时应正确处理错误状态', async () => {
  const mockError = { status: 404, message: '资源不存在' };
  httpClientMock.get.mockReturnValue(throwError(() => mockError));

  try {
    await firstValueFrom(service.getList());
  } catch (err) {
    expect(err).toEqual(mockError);
    // 如果服务内部有错误状态管理,可同步断言状态变更
    expect(service.requestError).toBe(true);
  }
});

场景3:测试热Observable(Subject/BehaviorSubject等状态流)

如果服务中用Subject类做状态管理,需要验证状态变更时所有订阅者能收到正确通知:

it('更新用户状态后应通知所有订阅者', (done) => {
  const mockUser = { id: 1, username: 'test_user' };
  // 先订阅要验证的状态流
  service.userInfo$.subscribe({
    next: (user) => {
      // 跳过BehaviorSubject的初始空值
      if (user) {
        expect(user).toEqual(mockUser);
        done();
      }
    }
  });
  // 触发状态更新操作
  service.updateUserInfo(mockUser);
});

常见注意事项

  • 如果待测试的Observable加了delay、debounceTime等时间相关操作符,可使用Angular提供的fakeAsync + tick模拟时间流逝,不需要等待真实时间
  • 对于多值发射的Observable,可以用数组收集所有发射的值,在complete回调中统一断言所有值是否符合预期
  • 不要在测试中直接订阅Observable而不加done回调或者await处理,会导致测试提前结束,断言不生效

内容的提问来源于stack exchange,提问作者Xyron mir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 07:54:08