如何用Jasmine测试NgbModal closed Observable中的回调函数
我正在开发基于Angular 14的项目,使用ng-bootstrap组件,测试框架为Jasmine。代码中的openModalGroup函数用于打开模态框添加分组(标签),模态框关闭时会调用refreshRecipients刷新收件人列表。目前Sonar显示该函数中refreshRecipients的调用未被测试覆盖,是组件最后几个未覆盖的代码点之一。
我尝试编写了如下测试代码:
fit('should close modal and refresh recipients', () => { const modalgroupRef = modalService.open(ModalGroupComponent, {windowClass: 'modal-default-form'}); modalgroupRef.componentInstance.groups = groups; spyOn(modalgroupRef, 'close'); spyOn(component, 'refreshRecipients'); modalgroupRef.close(); fixture.detectChanges(); expect(modalgroupRef.close).toHaveBeenCalled(); expect(component.refreshRecipients).toHaveBeenCalled(); })
我认为触发模态框关闭后,closed Observable应触发回调调用refreshRecipients,但测试未检测到该函数的调用。我还尝试过其他方法,比如在测试中订阅closed,但仍未成功检测到调用。
请问在现有实现下能否验证refreshRecipients是否被成功调用?还是需要重构代码以提升可测试性?
你的测试没命中核心逻辑——你直接调用了modalgroupRef.close(),但这个模态框实例并不是组件里openModalGroup函数创建的那个,而且你没有模拟openModalGroup内部对closed Observable的订阅逻辑。
现有实现下的正确测试方式
首先,你需要模拟NgbModal的open方法,让它返回一个带有closed Observable的模拟对象,然后调用组件的openModalGroup函数,再手动触发closed流的发射,这样才能触发回调里的refreshRecipients调用。
示例代码:
it('should call refreshRecipients when modal closes', () => { // 1. 创建模拟的closed Observable,用Subject来手动控制发射 const modalClosedSubject = new Subject<void>(); const mockModalRef = { componentInstance: { groups: [] }, closed: modalClosedSubject.asObservable() } as jasmine.SpyObj<NgbModalRef>; // 2. 模拟modalService.open返回这个mock实例 spyOn(modalService, 'open').and.returnValue(mockModalRef); // 3. 监听refreshRecipients spyOn(component, 'refreshRecipients'); // 4. 调用组件的openModalGroup函数(这才是你要测试的目标函数) component.openModalGroup(); // 5. 手动触发模态框关闭事件 modalClosedSubject.next(); fixture.detectChanges(); // 6. 验证refreshRecipients被调用 expect(component.refreshRecipients).toHaveBeenCalled(); });
为什么你的原测试失败?
- 你直接创建了
modalService.open的实例,但组件的openModalGroup函数会自己调用modalService.open,两者不是同一个实例,所以你触发的close事件和组件订阅的流完全无关。 - 你没有调用组件的
openModalGroup,等于跳过了核心的订阅逻辑,自然不会触发refreshRecipients。
是否需要重构?
如果你的openModalGroup逻辑是标准的ng-bootstrap模态框用法(比如:
openModalGroup() { const modalRef = this.modalService.open(ModalGroupComponent); modalRef.componentInstance.groups = this.groups; modalRef.closed.subscribe(() => { this.refreshRecipients(); }); }
),那不需要重构,用上面的测试方法就能覆盖到refreshRecipients的调用逻辑。如果你的逻辑更复杂(比如嵌套了多层订阅、或者把订阅逻辑藏在了其他地方),可以考虑把订阅逻辑抽成单独的方法,方便测试,但当前场景下完全不需要。
内容的提问来源于stack exchange,提问作者nurovek

