Angular单元测试:如何在函数执行完成后再调用断言语句
问题根因
你观察到的「断言先于component.submitFile调用」属于误判,实际上submitFile已经被同步执行,问题出在该方法内部的FileReader属于异步API:调用readAsXXX方法后,加载文件、像素处理、状态更新的逻辑会放到异步队列中执行,不会阻塞后续代码运行,所以你紧接着写的断言会在FileReader逻辑完成前执行,拿到的还是更新前的状态。
修复方案
方案1:修改业务代码暴露异步完成时机(推荐)
如果允许调整submitFile的实现,最稳妥的方式是让该方法返回Promise,把FileReader的完成时机对外暴露,方便测试等待:
// 业务组件中的submitFile修改为返回Promise submitFile(file: File): Promise<void> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { // 原有文件加载、像素处理、状态更新逻辑保留在此处 this.canBeProgressed = true; this.submited = false; resolve(); }; reader.onerror = reject; // 保留你原有读取文件的逻辑,比如readAsDataURL/readAsArrayBuffer等 reader.readAsDataURL(file); }) }
对应测试代码修改为等待Promise完成后再执行断言:
fit('should get local file and work with it', async (done: DoneFn) => { const resp = await httpMockService.get('/assets/testFiles/testImage.png', { responseType: 'blob' }).toPromise(); const file = new File([resp], 'logo.png', { type: 'image/png', lastModified: Date.now() } ); // 等待submitFile内部所有异步逻辑执行完成 await component.submitFile(file); expect(component.canBeProgressed).toEqual(true); expect(component.submited).toEqual(false); done(); });
方案2:不修改业务代码,用测试工具等待异步任务完成
如果不允许调整业务代码,可以使用Angular测试工具包提供的fakeAsync+flush,等待所有异步队列任务执行完成后再断言:
import { fakeAsync, flush } from '@angular/core/testing'; import { HttpClientTestingController } from '@angular/common/http/testing'; fit('should get local file and work with it', fakeAsync(() => { let file: File; // 发起模拟请求 httpMockService.get('/assets/testFiles/testImage.png', { responseType: 'blob' }).subscribe((resp: Blob) => { file = new File([resp], 'logo.png', { type: 'image/png', lastModified: Date.now() } ); component.submitFile(file); }); // 触发模拟HTTP请求返回,此处mockBlobData为你提前构造的测试用Blob数据 TestBed.inject(HttpClientTestingController) .expectOne('/assets/testFiles/testImage.png') .flush(mockBlobData); // 清空所有异步任务队列,等待FileReader等异步逻辑执行完成 flush(); expect(component.canBeProgressed).toEqual(true); expect(component.submited).toEqual(false); }));
注意事项
- 不要同时混用async/await、fakeAsync、done三种异步处理方式,选其中一种即可,避免出现时序混乱
- 如果你使用的是Angular官方的
HttpClientTestingModule做HTTP模拟,必须手动调用flush方法触发模拟请求返回,否则subscribe回调永远不会执行
内容的提问来源于stack exchange,提问作者dominikW
相关产品推荐
相关产品推荐

