Jasmine测试报错:点击登录按钮后Mock AuthService方法未触发排查
问题描述
刚开始用Jasmine,想创建Mock AuthService,期望点击表单按钮后触发点击处理器,进而调用Mock服务的方法,但测试报错:
Error:
: Expected a spy, but got Object({ authUser: spy on AuthService.authUser }). Usage: expect( ).toHaveBeenCalled()
之前手动传参调用方法的测试正常,现在要实现按钮触发的完整流程,相关代码如下:
测试代码
describe('Login submit', () => { let component: LoginComponent; let fixture: ComponentFixture<LoginComponent>; beforeEach(async () => { TestBed.configureTestingModule({ providers: [ AuthService, HttpClientTestingModule, HttpClientModule, HttpClient, HttpHandler, MatSnackBar, Overlay, FormBuilder, ], }); fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('authService should launch', () => { let mockAuthService = jasmine.createSpyObj('AuthService', ['authUser']); const mockUser = { usernsme: 'Marcus', password: 'Marcus#1' }; const compiled = fixture.debugElement; const loginBtn = compiled.query(By.css('.login-btn')).nativeElement; loginBtn.dispatchEvent(new Event('click')); expect(mockAuthService).toHaveBeenCalled(); }); });
LoginComponent的userLoginClick方法
userLoginClick() { if (!this.form.valid) { this.pushNotificationService.createNotification( 'Data format is incorrect' ); return false; } const user = { username: this.form.controls['username'].value, password: this.form.controls['password'].value, }; this.authService.authUser(user); }
AuthService的authUser方法
authUser(user: User) { return this.http .post( `${environment.domain}${BackendRoutes.Login}`, user, this.httpOptions ) .subscribe((data: any) => { if (!data.success) { this.pushNotificationService.createNotification(data.message); } else { this.router.navigate([`/${Paths.Lobby}`]); this.storeUser(data.token, data.user); } }); }
问题原因及修复方案
核心问题
- Mock服务没注入组件:你创建了
mockAuthService但没替换TestBed里的真实AuthService,组件用的还是原始服务实例,根本没用到你写的Spy对象。 - 断言对象错了:
expect(mockAuthService).toHaveBeenCalled()是对整个Spy对象做断言,但你要验证的是对象里的authUser方法,应该断言这个方法。 - 表单状态无效:点击按钮时表单控件是空的,会触发校验失败直接返回,根本走不到调用
authUser的逻辑。
修正后的测试代码
describe('Login submit', () => { let component: LoginComponent; let fixture: ComponentFixture<LoginComponent>; let mockAuthService: jasmine.SpyObj<AuthService>; beforeEach(async () => { // 创建AuthService的Spy对象 mockAuthService = jasmine.createSpyObj('AuthService', ['authUser']); await TestBed.configureTestingModule({ imports: [HttpClientTestingModule], // 测试环境只需要这个,其他冗余依赖可以删掉 providers: [ { provide: AuthService, useValue: mockAuthService }, // 用Mock替换真实服务 MatSnackBar, Overlay, FormBuilder, ], }).compileComponents(); // 异步编译组件(如果用了 templateUrl 必须加) fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; // 给表单控件赋值,确保表单校验通过 component.form.patchValue({ username: 'Marcus', password: 'Marcus#1' }); fixture.detectChanges(); // 触发变更检测,更新表单状态 }); it('should call authService.authUser when login button is clicked', () => { const loginBtn = fixture.debugElement.query(By.css('.login-btn')).nativeElement; loginBtn.dispatchEvent(new Event('click')); fixture.detectChanges(); // 触发点击后的变更检测 // 断言authUser方法被调用 expect(mockAuthService.authUser).toHaveBeenCalled(); // 可选:校验传入的参数是否正确 expect(mockAuthService.authUser).toHaveBeenCalledWith({ username: 'Marcus', password: 'Marcus#1' }); }); });
额外说明
- 去掉了
HttpClientModule、HttpClient这类冗余依赖,测试环境用HttpClientTestingModule足够。 - 调用
compileComponents()确保异步组件编译完成,避免组件未加载完全的问题。 - 通过
patchValue给表单赋值,保证userLoginClick里的校验逻辑能通过,顺利调用authUser。 - 把断言目标改成
mockAuthService.authUser,这才是你创建的Spy方法,符合Jasmine的断言规则。
内容的提问来源于stack exchange,提问作者Vova Aleshchenko
相关产品推荐
相关产品推荐

