在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
相关产品推荐
相关产品推荐

