Angular中NgbModal模拟异常,‘Modal Opened’测试失败求助
Angular测试NgbModal模拟失败的解决思路
核心问题原因
你的DeleteModalComponent在@Component元数据的providers数组里声明了NgbModalConfig和NgbModal,这会导致组件每次实例化时都会创建一个局部独立的NgbModal实例,完全绕过你在TestBed中配置的模拟对象。测试时你spy的modalService和组件实际调用的根本不是同一个实例,所以才会出现“open未被调用”的测试失败,同时也无法返回预期的modalRef结果。
解决步骤
1. 移除组件自身的NgbModal依赖声明
修改DeleteModalComponent的@Component装饰器,删除providers里的NgbModalConfig和NgbModal:
@Component({ selector: 'app-delete-modal', templateUrl: './delete-modal.component.html', styleUrls: ['./delete-modal.component.css'], // 删除此行:providers: [NgbModalConfig, NgbModal], }) export class DeleteModalComponent { @Output() readonly notify: EventEmitter<boolean> = new EventEmitter<boolean>(); constructor(private modalService: NgbModal, private toastr: ToastrService) { } open() { const modalRef: NgbModalRef = this.modalService.open(GroupDeleteModalComponent); modalRef.result.then((value) => { this.notify.emit(value); }).catch((error) => { console.error(error); this.toastr.error('Delete failed.'); }); } }
理由:NgbModal属于全局服务,应该由根模块(如AppModule)统一提供,组件自行声明会打破依赖注入的层级,导致测试无法替换真实实例。
2. 在TestBed中创建并提供NgbModal的Spy对象
在测试的beforeEach(async)块中,直接创建模拟的NgbModal对象,并配置其open方法的返回值:
beforeEach(async () => { const toastSpy = jasmine.createSpyObj('ToastrService', ['error']); // 创建NgbModal的Spy对象,模拟open方法 const modalSpy = jasmine.createSpyObj('NgbModal', ['open']); // 模拟modalRef的result属性,根据测试需求返回resolve或reject const mockModalRef = { result: Promise.resolve(true) // 测试成功分支用resolve,失败分支用Promise.reject('cancel') } as NgbModalRef; modalSpy.open.and.returnValue(mockModalRef); await TestBed.configureTestingModule({ declarations: [ DeleteModalComponent, GroupDeleteModalComponent, ], imports: [ HttpClientTestingModule, RouterTestingModule.withRoutes([]), // 移除NgbModule,因为我们使用模拟对象而非真实模块 ], providers: [ NgbActiveModal, { provide: ToastrService, useValue: toastSpy }, { provide: NgbModal, useValue: modalSpy }, // 注入模拟的NgbModal ], }).overrideModule(BrowserDynamicTestingModule, { set: { entryComponents: [GroupDeleteModalComponent], }, }).compileComponents(); });
3. 调整测试初始化逻辑
在第二个beforeEach中,直接获取TestBed提供的Spy对象,无需额外重复spy:
beforeEach(() => { modalService = TestBed.inject(NgbModal) as jasmine.SpyObj<NgbModal>; spyOn(console, 'log').and.callThrough(); fixture = TestBed.createComponent(DeleteModalComponent); component = fixture.componentInstance; fixture.detectChanges(); });
4. 验证测试用例
现在运行Modal Opened测试用例,就能正确检测到modalService.open被调用。如果需要测试result的then或catch分支,只需修改mockModalRef.result的值即可。
内容的提问来源于stack exchange,提问作者Jack Hurt
相关产品推荐
相关产品推荐

