Jasmine测试Angular组件时无法获取返回Observable的服务mock值
核心问题定位
- 错误的错误流返回方式:你用
of()包裹错误对象,只会触发subscribe的成功回调,不会触发错误回调,登录失败的逻辑自然走不到。需要用RxJS的throwError()来返回错误流,才能被组件的错误回调捕获。 - 不必要的BehaviorSubject包装:不需要用BehaviorSubject封装错误响应,直接返回HttpErrorResponse实例即可。
- 冗余的依赖引入:单元测试不需要导入
HttpClientModule,因为我们会mock掉AuthService,不会发起真实HTTP请求。 - 重复获取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
相关产品推荐
相关产品推荐

