如何在Karma测试中测试Angular Observable并mock switchMap与forkJoin
在Karma中测试包含switchMap和forkJoin的RxJS代码
不需要专门mock switchMap 和 forkJoin 两个RxJS操作符本身,直接mock它们依赖的上游数据流和HTTP服务即可完成测试。这种方案贴合代码真实运行逻辑,测试结果可信度更高,是单测的首选方案。
测试前置准备
需要提前mock两个核心依赖:
this.session.userStream:模拟用户流的输出值this.http:模拟两个GET接口的返回结果
以下是Angular + Karma + Jasmine环境的配置示例:
import { TestBed } from '@angular/core/testing'; import { HttpClient } from '@angular/common/http'; import { of, throwError } from 'rxjs'; import { YourService } from './your-service.path'; // 替换为实际业务服务路径 import { SessionService } from './session-service.path'; // 替换为实际Session服务路径 describe('YourService', () => { let service: YourService; let httpMock: jasmine.SpyObj<HttpClient>; const mockUser = { id: 123, name: 'Test User' }; beforeEach(() => { // 构造HttpClient的spy对象 const httpSpy = jasmine.createSpyObj('HttpClient', ['get']); TestBed.configureTestingModule({ providers: [ YourService, { provide: HttpClient, useValue: httpSpy }, { provide: SessionService, useValue: { userStream: of(mockUser) } } ] }); service = TestBed.inject(YourService); httpMock = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>; });
正常请求成功场景测试
验证接口调用逻辑、返回值和finalize回调执行效果:
it('should trigger two http requests and set isLoading to false after finish', (done) => { // Mock两个接口的返回值 const mockUserResp = { id: 123, name: 'Test User', phone: '13xxxxxxxxx' }; const mockCardResp = { id: 456, type: 'platinum' }; httpMock.get.withArgs(`/users/${mockUser.id}`).and.returnValue(of(mockUserResp)); httpMock.get.withArgs(`/users/${mockUser.id}/card-program`).and.returnValue(of(mockCardResp)); // 调用实际业务方法 service.yourBusinessMethod(); // 替换为实际业务方法名 // 验证接口调用参数 expect(httpMock.get).toHaveBeenCalledTimes(2); expect(httpMock.get).toHaveBeenCalledWith(`/users/${mockUser.id}`); expect(httpMock.get).toHaveBeenCalledWith(`/users/${mockUser.id}/card-program`); // 验证返回值和finalize逻辑 service.userStream // 替换为实际业务代码中订阅的Observable变量 .subscribe({ next: (resp) => { expect(resp).toEqual([mockUserResp, mockCardResp]); }, complete: () => { expect(service.isLoading).toBeFalse(); done(); } }); });
请求异常场景测试
验证接口报错时finalize依然可以正常执行:
it('should set isLoading to false even if request failed', (done) => { httpMock.get.withArgs(`/users/${mockUser.id}`).and.returnValue(throwError(() => new Error('Request failed'))); httpMock.get.withArgs(`/users/${mockUser.id}/card-program`).and.returnValue(of({})); service.yourBusinessMethod(); service.userStream .subscribe({ error: (err) => { expect(err.message).toBe('Request failed'); expect(service.isLoading).toBeFalse(); done(); } }); });
不推荐方案:直接mock操作符
如果有特殊场景需要直接mock操作符,可以通过spy RxJS导出方法实现,该方案会跳过操作符真实执行逻辑,测试可信度极低,非特殊情况不要使用:
import * as rxjs from 'rxjs'; import * as operators from 'rxjs/operators'; it('mock switchMap and forkJoin directly', () => { const switchMapSpy = spyOn(operators, 'switchMap').and.returnValue((source) => source); const forkJoinSpy = spyOn(rxjs, 'forkJoin').and.returnValue(of([mockUserResp, mockCardResp])); // 执行业务逻辑... expect(switchMapSpy).toHaveBeenCalled(); expect(forkJoinSpy).toHaveBeenCalled(); });
内容的提问来源于stack exchange,提问作者Frank Mendez
相关产品推荐
相关产品推荐

