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

Angular测试:Mock服务桩传复杂对象 解决嵌套属性undefined报错

问题根因

这问题和of()的内部运行机制没有关系,核心错误是你没有订阅getUser()返回的Observable,直接把Observable实例当成了实际返回的用户对象使用。
你认为前两行执行正常完全是错觉:

  • 第一行把of()创建的Observable实例赋值给this.activeTokens,属于合法赋值操作,不会报错
  • 第二行访问Observable实例上不存在的username属性,拿到的值是undefined,将undefined赋值给类属性本身不会抛错,只是值完全不符合预期,你没有做值校验就误以为执行成功
  • 到第三行时,你先读取this.activeTokens.attributes,Observable实例上没有这个自定义属性,拿到undefined,再继续读取undefined上的organizationId属性,就触发了你看到的类型错误,和你mock数据中定义的attributes字段没有任何关联。
代码修正方案

根据你的服务设计选择对应写法即可:

场景1:真实服务的getUser()为异步返回Observable

这是Angular服务处理token逻辑的常规设计,你必须在订阅回调中拿到Observable发射的实际值,所有依赖用户对象的逻辑都要放到订阅回调里:

ngOnInit(): void {
  this.token.getUser().subscribe(currentUser => {
    this.activeTokens = currentUser;
    this.username = currentUser.username;
    this.organization.id = currentUser.attributes.organizationId;
    // 其余依赖用户信息的赋值逻辑统一放在这里
  });
}

记得在组件的ngOnDestroy生命周期里取消订阅,避免内存泄漏。

场景2:真实服务的getUser()为同步返回本地存储值

如果你的服务不需要异步处理,是直接同步返回本地缓存的token对象,那测试桩里多余包了一层of(),直接返回对象即可:

tokenStorageServiceStub = { // Mock service data
  getUser: () => ({
    username: 'organization-a-a-normal',
    roles: ['PROCESSOR'],
    attributes: {
      organizationId: 111,
      organizationName: 'Org.name Aa',
      userFullName: 'User for organization A-A',
      userId: 22,
    },
    access_token: 'AT01',
    refresh_token: 'RT01',
    token_type: 'Bearer',
    expires_in: 3600,
  }),
};
关于of()的废弃说明

你听到的废弃传言不准确。目前RxJS官方没有废弃of()创建函数的计划,只是在v7版本移除了of()支持传入resultSelector参数的重载,传入普通对象、基础类型值直接发射的用法完全稳定,测试中用它mock同步返回的异步数据流没有任何问题。

测试桩Mock服务的最佳实践
  • 严格对齐真实服务的返回类型:真实方法返回Observable就用of()/throwError()等返回对应流,真实方法同步返回值就直接返回值,不要随意改变返回类型
  • 优先用Spy创建mock方法,方便不同测试用例自定义返回值、校验调用情况,比写死stub灵活度高很多:
// 初始化时创建带spy的mock服务
tokenStorageServiceStub = jasmine.createSpyObj('TokenStorageService', ['getUser']);

// 单个测试用例中自定义返回值
it('should load user info on init', () => {
  const mockUser = {
    // 你的mock用户数据
  };
  tokenStorageServiceStub.getUser.and.returnValue(of(mockUser));
  component.ngOnInit();
  // 触发订阅后校验赋值逻辑
  expect(component.username).toBe(mockUser.username);
  expect(component.organization.id).toBe(mockUser.attributes.organizationId);
});
  • 如果需要测试加载态、错误分支、多次触发的逻辑,可以用Subject代替of(),手动控制流的发射、报错、完成时机,覆盖更多测试场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 13:27:22