Angular中如何在Jasmine里正确Mock Amplify进行单元测试?
如何正确Mock Amplify Auth解决Angular单元测试异步超时问题
别慌!刚上手单元测试碰到第三方库Mock的问题太正常了,我帮你捋清楚怎么正确Mock Amplify Auth来解决这个超时问题~
先说说你当前代码的问题
你遇到的Async function did not complete within 5000ms错误,核心原因有两个:
- 你Mock的
Amplify.Auth.signIn直接返回了普通对象,但你的login方法用await等待它——await只对Promise生效,这个普通对象没法触发resolve,导致测试一直卡在等待状态。 - 你直接SpyOn了全局的
Amplify.Auth,但TestBed里提供的是原始Amplify实例,两者没有关联,测试里的服务实际调用的还是未Mock的方法。
正确的Mock步骤
1. 创建Mock版的Amplify对象
我们需要让Auth.signIn返回一个resolved状态的Promise,同时把这个Mock对象注入到测试模块中:
// 定义Mock的Amplify,包含我们需要的Auth方法 const mockAmplify = { Auth: { signIn: jasmine.createSpy('signIn').and.returnValue(Promise.resolve({ signInUserSession: { idToken: { jwtToken: 'tokenValue' } } })) } };
如果你的项目用的是Jest,把jasmine.createSpy换成jest.fn(),returnValue换成mockResolvedValue即可。
2. 在TestBed中注入Mock的Amplify
不要直接提供原始的Amplify,而是用useValue替换成我们的Mock对象,同时记得调用compileComponents()(异步配置测试模块的必要步骤):
let myService: MyService; let amplify: typeof Amplify; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ HttpClientTestingModule ], providers: [ MyService, // 用Mock对象替代原始Amplify { provide: Amplify, useValue: mockAmplify } ] }).compileComponents(); // 推荐用inject替代get,Angular 9+更推荐这个API myService = TestBed.inject(MyService); amplify = TestBed.inject(Amplify); });
3. 调整测试用例,添加必要断言
现在测试可以正常执行了,我们还可以加上断言验证方法的调用逻辑:
it('should login successfully and trigger patientLogin with correct token', async () => { // Mock私有方法patientLogin,避免实际发起HTTP请求 spyOn(myService as any, 'patientLogin').and.returnValue(Promise.resolve(true)); // 调用login方法 const loginResult = await myService.login('USERNAME', 'password'); // 验证Auth.signIn是否被正确调用(注意username被转成小写了) expect(amplify.Auth.signIn).toHaveBeenCalledWith('username', 'password'); // 验证patientLogin是否传入了正确的token expect((myService as any).patientLogin).toHaveBeenCalledWith('tokenValue'); // 验证login方法返回true expect(loginResult).toBe(true); });
4. 处理patientLogin中的HTTP请求(可选但推荐)
如果你想完整测试整个登录流程,包括patientLogin里的HTTP请求,可以用HttpClientTestingModule的HttpTestingController来捕获并Mock响应:
let httpTestingController: HttpTestingController; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ HttpClientTestingModule ], providers: [ MyService, { provide: Amplify, useValue: mockAmplify } ] }).compileComponents(); myService = TestBed.inject(MyService); amplify = TestBed.inject(Amplify); httpTestingController = TestBed.inject(HttpTestingController); }); // 测试后验证所有HTTP请求都被处理,避免遗漏 afterEach(() => { httpTestingController.verify(); }); it('should complete full login flow including HTTP request', async () => { const mockPatientToken = 'mock-patient-token'; const mockLoginResponse = { token: mockPatientToken }; // 调用login方法 await myService.login('USERNAME', 'password'); // 捕获sendRequest发出的HTTP请求(替换成你实际的接口URL) const request = httpTestingController.expectOne('/your-login-api-url'); // 验证请求方法是否正确(假设是POST) expect(request.request.method).toBe('POST'); // 返回Mock的响应 request.flush(mockLoginResponse); // 验证服务的token是否被正确设置 expect(myService['token']).toBe(mockPatientToken); });
最后总结
核心就是要让Mock的signIn返回Promise,同时确保测试模块中使用的是Mock的Amplify实例,这样await就能正常获取返回值,不会导致超时。按照上面的步骤调整后,你的测试应该就能正常跑通啦~
内容的提问来源于stack exchange,提问作者Tommazo
相关产品推荐
相关产品推荐

