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

Angular 19导航栏组件logout集成测试失败,请求排查

Angular导航栏组件Logout功能测试失败排查

问题概述

导航栏组件在未登录时显示Login和Signup链接,登录时显示My Account和Logout链接。编写集成测试验证点击Logout链接是否调用logout方法,但测试失败,浏览器中功能正常。

组件代码

<nav class="navbar">
    <a routerLink="/" class="logo">E-Market</a>
    <ul class="nav-links">
      <li><a routerLink="/products">Products</a></li>
      <li id="login" *ngIf="!isLoggedIn"><a routerLink="/auth/login">Login</a></li>
      <li id="sign-up" *ngIf="!isLoggedIn"><a routerLink="/auth/sign-up">Sign up</a></li>
      <li id="account" *ngIf="isLoggedIn"><a routerLink="/account">My account</a></li>
      <li id="logout" *ngIf="isLoggedIn"><a (click)="logout()">Logout</a></li>
    </ul>
  </nav>
import { Component, effect } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from 'src/app/core/services/auth.service';

@Component({
    selector: 'app-navbar',
    templateUrl: './navbar.component.html',
    styleUrls: ['./navbar.component.scss'],
    standalone: false
})
export class NavbarComponent {
  isLoggedIn!: boolean;
  
  constructor(private authService: AuthService) {
    effect(() => {
      this.isLoggedIn = this.authService.isLoggedIn();
    });
  }
  
  logout() {
    this.authService.logout();
  }
}

测试代码(原错误版本)

import { ComponentFixture, TestBed, tick } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
import { AuthService } from 'src/app/core/services/auth.service';
import { Router } from '@angular/router';

describe('NavbarComponent', () => {
  let component: NavbarComponent;
  let fixture: ComponentFixture<NavbarComponent>;
  let authServiceSpy: jasmine.SpyObj<AuthService>;
  let routerSpy: jasmine.SpyObj<Router>;

  beforeEach(() => {
    authServiceSpy = jasmine.createSpyObj('AuthService', ['isLoggedIn', 'logout']);
    routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);

    TestBed.configureTestingModule({
      declarations: [NavbarComponent],
      providers: [
        { provide: AuthService, useValue: authServiceSpy },
        { provide: Router, useValue: routerSpy },
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(NavbarComponent);
    component = fixture.componentInstance;
  });

  it('should call logout method when clicking on "Logout"', () => {
    authServiceSpy.isLoggedIn.and.returnValue(true);
    fixture.detectChanges();
    component = fixture.componentInstance;
    spyOn(component, 'logout').and.callThrough();;

    const logoutLink = fixture.nativeElement.querySelector('#logout');
    logoutLink.click();
    expect(component.logout).toHaveBeenCalled();
    expect(authServiceSpy.logout).toHaveBeenCalled();
    expect(routerSpy.navigateByUrl).toHaveBeenCalledWith('/');
  });
});

测试错误信息

Chrome 131.0.0.0 (Windows 10) NavbarComponent should call logout method when clicking on "Logout" FAILED
Expected spy logout to have been called.
at
at UserContext.apply (src/app/navbar/components/navbar/navbar.component.spec.ts:67:30)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2082:39)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34)
Expected spy AuthService.logout to have been called.
at
at UserContext.apply (src/app/navbar/components/navbar/navbar.component.spec.ts:68:35)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2082:39)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34)
Expected spy Router.navigateByUrl to have been called with:
[ '/' ]
but it was never called.
at
at UserContext.apply (src/app/navbar/components/navbar/navbar.component.spec.ts:69:37)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:369:28)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:2082:39)
at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:368:34)

问题分析与修复

错误点1:错误的路由跳转期望

组件的logout方法仅调用authService.logout(),未执行路由跳转逻辑,测试中错误添加了router.navigateByUrl的断言,这会导致该断言必然失败。

错误点2:Spy时机与组件初始化顺序错误

原测试中在fixture.detectChanges()(组件初始化)后才对component.logout设置Spy,此时模板已绑定原始的logout方法,点击事件触发的是原始方法而非Spy方法,导致断言component.logout被调用失败。

错误点3:冗余的组件实例赋值

component = fixture.componentInstance;在beforeEach中已完成赋值,测试中重复赋值无意义,应删除。

修复后的测试代码

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarComponent } from './navbar.component';
import { AuthService } from 'src/app/core/services/auth.service';
import { Router } from '@angular/router';

describe('NavbarComponent', () => {
  let component: NavbarComponent;
  let fixture: ComponentFixture<NavbarComponent>;
  let authServiceSpy: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    authServiceSpy = jasmine.createSpyObj('AuthService', ['isLoggedIn', 'logout']);

    TestBed.configureTestingModule({
      declarations: [NavbarComponent],
      providers: [
        { provide: AuthService, useValue: authServiceSpy },
        { provide: Router, useValue: jasmine.createSpyObj('Router', ['navigateByUrl']) },
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(NavbarComponent);
    component = fixture.componentInstance;
  });

  it('should call logout method when clicking on "Logout"', () => {
    // 1. 设置登录状态为已登录
    authServiceSpy.isLoggedIn.and.returnValue(true);
    
    // 2. 在组件初始化前,对logout方法设置Spy
    spyOn(component, 'logout').and.callThrough();
    
    // 3. 初始化组件,触发变更检测与effect执行
    fixture.detectChanges();
    
    // 4. 确认Logout链接存在
    const logoutLink = fixture.nativeElement.querySelector('#logout');
    expect(logoutLink).toBeTruthy('Logout链接未渲染,检查isLoggedIn状态是否正确');
    
    // 5. 点击Logout链接
    logoutLink.click();
    
    // 6. 触发变更检测,确保事件处理完成
    fixture.detectChanges();
    
    // 7. 断言
    expect(component.logout).toHaveBeenCalled();
    expect(authServiceSpy.logout).toHaveBeenCalled();
  });
});

额外说明(若AuthService使用Signal)

如果AuthService.isLoggedIn()返回的是Angular Signal而非普通布尔值,测试中需返回Signal实例:

import { signal } from '@angular/core';

// 在beforeEach中修改spy返回值
authServiceSpy.isLoggedIn.and.returnValue(signal(true));

// 同时组件中的effect需正确读取Signal值:
effect(() => {
  this.isLoggedIn = this.authService.isLoggedIn()(); // 调用Signal获取值
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:25:55