基于Jasmine与HttpClientTestingModule的Ionic+Angular 6登录单元测试及API调用无响应问题排查
解决Angular/Ionic单元测试中异步登录方法无响应的问题
看起来你遇到的核心问题是单元测试中异步操作没有被正确处理,同时依赖的服务没有被Mock,导致真实HTTP请求卡住了Promise,所以doLogin()的then回调永远不会执行,console.log也看不到输出。下面我帮你一步步解决这个问题:
问题根源分析
- 未Mock依赖服务:你在测试中直接注入了真实的
AuthService和HttpService,这会导致测试时真的向reqres.in发送请求。Karma的测试环境通常无法正确处理外部HTTP请求,要么请求超时,要么被拦截,导致toPromise()一直处于pending状态,await永远等不到结果。 - 异步测试未正确等待:测试用例F中没有使用异步测试的写法(比如
async/await或done回调),Karma会认为测试同步完成,直接结束,不会等待doLogin()的Promise resolve。
解决方案
1. Mock AuthService,避免真实HTTP请求
在测试的beforeEach中,我们需要MockAuthService的login方法,让它返回一个模拟的成功响应,这样就不会触发真实的HTTP调用了。修改你的测试配置:
beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ LoginPage ], imports: [IonicModule.forRoot(), FormsModule, ReactiveFormsModule, HttpClientTestingModule], // 替换成Mock的AuthService providers: [ { provide: AuthService, useValue: { login: jasmine.createSpy('login').and.returnValue(Promise.resolve({ token: 'mock-token' } as Credentials)) } } // 不需要真实的HttpService了,因为AuthService已经被Mock ] }).compileComponents(); fixture = TestBed.createComponent(LoginPage); component = fixture.componentInstance; authService = TestBed.inject(AuthService); // 获取mock的AuthService fixture.detectChanges(); jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; }));
2. 修复异步测试用例的写法
对于测试用例F,我们需要用async/await或者done回调来等待异步操作完成。推荐用async/await,写法更清晰:
it('F) send form successfully', async(() => { component.ionicForm = component.formBuilder.group({ email: ['eve.holt@reqres.in', [Validators.required, Validators.minLength(8)]], password: ['cityslicka', [Validators.required, Validators.minLength(8)]], }); const result = await component.doLogin(); console.log(result); // 现在这个log会正常输出了 expect(result).toEqual({ token: 'mock-token' }); expect(authService.login).toHaveBeenCalledWith(component.ionicForm.value); }));
3. 其他测试用例的异步修复
你的测试用例B-E中也有同样的问题:doLogin().then(...)的回调不会被执行,因为测试已经同步结束了。同样需要改成async/await的写法,比如测试用例B:
it('B) send form without data', async(() =>{ component.ionicForm = component.formBuilder.group({ email: ['', [Validators.required, Validators.minLength(8)]], password: ['', [Validators.required, Validators.minLength(8)]], }); const data = await component.doLogin(); expect(data).toBeFalse(); fixture.detectChanges(); expect(fixture.debugElement.query(By.css('.errorUsernameRequired'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('.errorPasswordRequired'))).not.toBeNull(); }));
为什么这样修改有效?
- Mock服务:我们用Jasmine的
createSpy创建了一个假的login方法,它会立即返回一个resolved的Promise,这样await this._authService.login(...)会马上得到结果,不会卡住。 - 异步等待:用
async/await包裹测试逻辑,Karma会等待所有异步操作完成后再结束测试,确保then或者await后的代码都能执行。
额外建议
如果你以后需要测试HttpService本身,可以用HttpClientTestingModule的HttpTestingController来Mock HTTP请求,而不是直接调用真实接口。比如测试AuthService的login方法时,可以这样做:
import { HttpTestingController } from '@angular/common/http/testing'; // ... 在测试中 let httpTestingController: HttpTestingController; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [AuthService, HttpService] }); httpTestingController = TestBed.inject(HttpTestingController); }); it('should call login API', async(() => { const authService = TestBed.inject(AuthService); const mockBody = { email: 'test@test.com', password: 'test' }; const mockResponse = { token: 'test-token' }; const result = await authService.login(mockBody); expect(result).toEqual(mockResponse); const req = httpTestingController.expectOne('https://reqres.in/api/login'); expect(req.request.method).toBe('POST'); expect(req.request.body).toEqual(mockBody); req.flush(mockResponse); }));
内容的提问来源于stack exchange,提问作者Nicolas Papp
相关产品推荐
相关产品推荐

