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

