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

Jasmine测试Angular组件时无法获取返回Observable的服务mock值

核心问题定位

  1. 错误的错误流返回方式:你用of()包裹错误对象,只会触发subscribe的成功回调,不会触发错误回调,登录失败的逻辑自然走不到。需要用RxJS的throwError()来返回错误流,才能被组件的错误回调捕获。
  2. 不必要的BehaviorSubject包装:不需要用BehaviorSubject封装错误响应,直接返回HttpErrorResponse实例即可。
  3. 冗余的依赖引入:单元测试不需要导入HttpClientModule,因为我们会mock掉AuthService,不会发起真实HTTP请求。
  4. 重复获取AuthService实例:fixture.debugElement.injector.get(AuthService)和TestBed.get(AuthService)功能重复,保留一个即可。

修正后完整测试代码

import { HttpErrorResponse } from '@angular/common/http';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { throwError, of } from 'rxjs';
import { AuthService } from '../service/auth.service';
import { LoginComponent } from './login.component';
import { Router } from '@angular/router';

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let authService: jasmine.SpyObj<AuthService>;
  let router: jasmine.SpyObj<Router>;

  beforeEach(async(() => {
    // 提前创建mock服务实例,隔离外部依赖
    const authSpy = jasmine.createSpyObj('AuthService', ['login']);
    const routerSpy = jasmine.createSpyObj('Router', ['navigate']);

    TestBed.configureTestingModule({
      declarations: [ LoginComponent ],
      imports: [FormsModule, RouterTestingModule],
      providers: [
        { provide: AuthService, useValue: authSpy },
        { provide: Router, useValue: routerSpy }
      ]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    // 注入mock实例
    authService = TestBed.inject(AuthService) as jasmine.SpyObj<AuthService>;
    router = TestBed.inject(Router) as jasmine.SpyObj<Router>;
    fixture.detectChanges();
  });

  it('登录失败时展示错误提示', async(() => {
    // 构造错误响应,用throwError返回错误流
    const mockError = new HttpErrorResponse({
      error: { errorMessage: "Invalid credentials"}
    });
    authService.login.and.returnValue(throwError(() => mockError));

    // 填充表单数据
    component.email = 'vadine.asyw@infas.com';
    component.password = 'pasywdja';
    
    // 触发提交
    component.submitForm();
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      // 校验入参是否正确
      expect(authService.login).toHaveBeenCalledWith({
        username: 'vadine.asyw@infas.com',
        password: 'pasywdja'
      });
      // 校验组件变量赋值正确
      expect(component.message).toBe('Invalid credentials');
      // 校验页面渲染正确
      const msgElem = fixture.debugElement.nativeElement.querySelector("#message");
      expect(msgElem).not.toBeNull();
      expect(msgElem.textContent.trim()).toContain("Invalid credentials");
    });
  }));

  it('登录成功时跳转到仪表盘页', async(() => {
    // 构造成功响应
    const mockSuccessResp = {
      status: 200,
      JWT_Token: "y87y21skj.dh8712haskhdaksdj.haasdyusd"
    };
    authService.login.and.returnValue(of(mockSuccessResp));

    // 填充表单数据
    component.email = 'test@example.com';
    component.password = 'validpass';
    
    // 触发提交
    component.submitForm();
    fixture.detectChanges();

    fixture.whenStable().then(() => {
      expect(authService.login).toHaveBeenCalledWith({
        username: 'test@example.com',
        password: 'validpass'
      });
      // 校验sessionStorage存储正确
      expect(sessionStorage.getItem('UserAuthenticated')).toBe('true');
      expect(sessionStorage.getItem('Jwt_Token')).toBe(mockSuccessResp.JWT_Token);
      // 校验路由跳转正确
      expect(router.navigate).toHaveBeenCalledWith(['/dashboard']);
    });
  }));
});

关键改动说明

  • 提前创建服务的Spy对象替代真实实例,完全隔离外部依赖,测试稳定性更高
  • 登录失败场景用throwError()返回错误流,正确触发组件的错误回调逻辑
  • 登录成功场景新增了sessionStorage存储校验和路由跳转校验,覆盖全部成功逻辑
  • 移除了不必要的依赖导入和重复的实例获取逻辑
  • 新增了参数调用校验,确保传给login方法的参数符合预期

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 23:36:03