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

如何测试含Observable订阅且无需触发值发射的服务?

如何在单元测试中阻止Observable订阅回调执行?

问题场景

我有一个MyService服务,其构造函数中订阅了依赖AuthService的events Observable字段。编写单元测试时,我希望忽略该订阅——即在部分测试中不触发Observable发射值,因为要测试的是无关函数。但当前测试中,订阅的回调仍会执行,而我不想也无需MockrefreshToken方法。

相关代码

MyService

export class MyService {
  constructor(private authService: AuthService) {
    this.authService.configure();
    this.authService.events.subscribe(async (event) => {
      await this.authService.refreshToken();
    });
  }
}

AuthService(第三方依赖片段)

export declare class AuthService implements OnDestroy {
    events: Observable<Event>;
    // ... 其他字段/方法
}

当前测试代码

describe('MyService', () => {
  let authServiceSpy: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    authServiceSpy = jasmine.createSpyObj<AuthService>(
      ['configure', 'refreshToken'],
      {
        get events() { return defer(() => Promise.resolve(new Event('some_event'))); }
      }
    );

    TestBed.configureTestingModule({});
  });

  it('should create service and configure authService', () => {
    const myService = new MyService(authServiceSpy);
    expect(myService).toBeTruthy();
    expect(authServiceSpy.configure).toHaveBeenCalledOnce();
  });
});

解决方案

核心是让authServiceSpy.events返回一个永远不会发射值的Observable,这样订阅后的回调逻辑就不会被触发。RxJS提供了两个最适合的工具:

  • EMPTY:立即完成但不发射任何值,订阅后直接进入完成阶段,不会执行next回调
  • never():永远处于等待状态,既不发射值也不完成,同样不会触发回调

修改后的测试代码

把原来events的返回值替换成EMPTY或never()即可:

import { EMPTY, never } from 'rxjs'; // 记得导入RxJS操作符

describe('MyService', () => {
  let authServiceSpy: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    authServiceSpy = jasmine.createSpyObj<AuthService>(
      ['configure', 'refreshToken'],
      {
        // 方案1:用EMPTY,语义化表示“无值发射”
        get events() { return EMPTY; }
        // 方案2:用never(),永远不触发任何回调
        // get events() { return never(); }
      }
    );

    TestBed.configureTestingModule({});
  });

  it('should create service and configure authService', () => {
    const myService = new MyService(authServiceSpy);
    expect(myService).toBeTruthy();
    expect(authServiceSpy.configure).toHaveBeenCalledOnce();
    // 可以加这个断言验证refreshToken确实没被调用
    expect(authServiceSpy.refreshToken).not.toHaveBeenCalled();
  });
});

为什么原来的写法不行?

你之前用defer(() => Promise.resolve(new Event('some_event'))),会让Observable立即发射一个Event值,所以订阅的回调函数会被执行。换成EMPTY或never()后,Observable不会发射任何值,自然不会触发回调。

灵活扩展

如果后续某些测试用例需要让events发射值,只需在对应测试中覆盖authServiceSpy.events的返回值即可:

it('should handle auth event', () => {
  // 临时让events发射测试事件
  authServiceSpy.events = of(new Event('test_event'));
  const myService = new MyService(authServiceSpy);
  // 这里可以编写对应逻辑的断言
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 15:05:30