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

Angular Auth服务单元测试成功场景未通过问题求助

Angular单元测试排坑:Auth服务isAuthenticated成功场景测试失败

问题背景

我是Angular新手,在给Auth服务的isAuthenticated方法写单元测试时,成功场景一直跑不通,下面是我的代码和报错情况:

Auth服务核心代码

isAuthenticated(): Observable<any>{
    let token = String(localStorage.getItem('token'));
    return this.http.post<any>(`${environment.apiURL}/verify`,{ token: token }, {observe: 'response'})
      .pipe(
        map( (res:any) =>{
            if (res.body.user.imageURL) {
              res.user.imageURL = environment.hostURL + res.body.user.imageURL;
            }
            let user: User = res.body.user;
            this.userService.setCurrentUser(user)
            return of({ res: true , message: 'token is valid' })
        }),
        catchError(err=>{
          return of({ res: false , message: 'token is not valid' })
        })
      ) 
}

单元测试代码

it('should return success  result', (done: DoneFn) => {
    const mockSuccessResponse = { res: true, message: 'token is valid' };
    httpClientSpy.post.and.returnValue(asyncData(mockSuccessResponse));
      authService.isAuthenticated().subscribe({
      next: res => {
        expect(res)
          .withContext('expected login result')
          .toEqual(mockSuccessResponse);
        done();
      },
      error: done.fail
    });
    expect(httpClientSpy.post.calls.count())
      .withContext('one call')
      .toBe(1);  
});

报错截图

单元测试未通过


问题根源&修复方案

1. HTTP响应结构不匹配

你的服务里设置了{observe: 'response'},这表示HttpClient会返回完整的HTTP响应对象(带body、status等字段),但测试里直接返回了最终要输出的结果对象,完全不符合服务代码的预期——服务的map操作里要访问res.body.user,但你mock的返回值根本没有body属性,直接就报错了。

2. map操作错误包装Observable

map操作符的作用是转换流中的值,应该直接返回普通对象,你却用of()把结果包成了Observable,导致最终流里发出的是Observable而不是预期的对象,订阅的时候自然拿不到正确的值。

3. 服务代码还有笔误

原代码里的res.user.imageURL是错的,应该是res.body.user.imageURL,不然运行时也会找不到这个属性。


修复后的服务代码(关键修改点)

map( (res:any) =>{
    if (res.body.user.imageURL) {
      // 修正笔误:res.body.user.imageURL 而非 res.user.imageURL
      res.body.user.imageURL = environment.hostURL + res.body.user.imageURL;
    }
    let user: User = res.body.user;
    this.userService.setCurrentUser(user);
    // 直接返回对象,不用of()包装
    return { res: true , message: 'token is valid' };
}),

修复后的单元测试代码

it('should return success result', (done: DoneFn) => {
    // 模拟符合要求的完整HTTP响应对象
    const mockHttpResponse = {
      body: {
        user: {
          imageURL: '/user-avatar.jpg' // 可选,根据业务需要设置
        }
      }
    };
    const expectedResult = { res: true, message: 'token is valid' };
    
    // 1. Mock HttpClient的post返回值
    httpClientSpy.post.and.returnValue(asyncData(mockHttpResponse));
    // 2. Mock localStorage,避免拿到null转成的字符串"null"
    spyOn(localStorage, 'getItem').and.returnValue('test-auth-token');
    // 3. Mock UserService的方法,避免测试依赖其他服务
    spyOn(userService, 'setCurrentUser').and.stub();

    authService.isAuthenticated().subscribe({
      next: res => {
        // 验证返回结果是否符合预期
        expect(res).toEqual(expectedResult);
        // 验证UserService的方法是否被正确调用
        expect(userService.setCurrentUser).toHaveBeenCalledWith(mockHttpResponse.body.user);
        done();
      },
      error: done.fail
    });

    // 验证请求次数和参数是否正确
    expect(httpClientSpy.post.calls.count()).toBe(1);
    expect(httpClientSpy.post).toHaveBeenCalledWith(
      `${environment.apiURL}/verify`,
      { token: 'test-auth-token' },
      { observe: 'response' }
    );
});

额外提醒

  • 单元测试要尽可能隔离依赖,所以localStorage和userService都需要mock,避免测试受外部因素影响。
  • 要验证请求的参数、调用次数,确保服务的逻辑符合预期,而不只是验证返回结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 07:34:51