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

基于Jasmine与HttpClientTestingModule的Ionic+Angular 6登录单元测试及API调用无响应问题排查

解决Angular/Ionic单元测试中异步登录方法无响应的问题

看起来你遇到的核心问题是单元测试中异步操作没有被正确处理,同时依赖的服务没有被Mock,导致真实HTTP请求卡住了Promise,所以doLogin()的then回调永远不会执行,console.log也看不到输出。下面我帮你一步步解决这个问题:

问题根源分析

  1. 未Mock依赖服务:你在测试中直接注入了真实的AuthService和HttpService,这会导致测试时真的向reqres.in发送请求。Karma的测试环境通常无法正确处理外部HTTP请求,要么请求超时,要么被拦截,导致toPromise()一直处于pending状态,await永远等不到结果。
  2. 异步测试未正确等待:测试用例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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 17:02:46