如何Mock BsModalRef的onClose.subscribe以覆盖Angular模态框测试代码
解决方案
核心逻辑是构造符合ModalConfirmComponent结构的mock content对象,将onClose替换为可主动触发的Subject,测试时手动发射值即可触发订阅内的逻辑。
步骤1:调整ModalService的mock实现
在测试配置中,提前声明可控制的onClose Subject,修改modalService.show的返回值:
// 测试用例外层声明,用于主动控制模态框返回值 let mockModalOnClose: Subject<boolean | undefined>; // spyOn替换show方法的返回值 spyOn(mockModalService, 'show').and.callFake(() => { mockModalOnClose = new Subject<boolean | undefined>(); return { id: 1, content: { // 空实现组件方法,避免调用时报错 showConfirmationModal: jasmine.createSpy('showConfirmationModal'), onClose: mockModalOnClose }, hide: jasmine.createSpy('hide'), setClass: jasmine.createSpy('setClass'), onHide: new EventEmitter(), onHidden: new EventEmitter(), }; });
步骤2:修改测试用例,主动触发订阅逻辑
分场景测试确认/取消两种分支,示例测试确认添加元素的场景:
describe('addElementWithConfirmation()', () => { it('should add element when confirm modal is confirmed', () => { spyOn(service, 'canCourseContentElementBeAdded').and.returnValue(true); spyOn(service, 'hideAddElementMenu').and.callThrough(); // 提前获取ADD方法的引用,验证是否被调用 const addMethodSpy = spyOn(ADD_COURSE_CONTENT_ELEMENT_MAP, CourseContentElementsMap.ADD_COURSE_CONTENT_ELEMENT_AUDIO).and.callThrough(); const newCourseContents: ICourseContent[] = JSON.parse(JSON.stringify(courseContents)); const testContentUid = newCourseContents[0].uid; const testElementUid = `${CourseContentElementType.AUDIO}-${UtilService.generateRandomString(10)}`; // 调用目标方法 service.addElementWithConfirmation( course, newCourseContents, testContentUid, testElementUid, 0, true, CourseContentElementsMap.ADD_COURSE_CONTENT_ELEMENT_AUDIO, 'test' ); // 手动触发模态框确认事件,发射true mockModalOnClose.next(true); // 验证逻辑执行结果 expect(service.canCourseContentElementBeAdded).toHaveBeenCalled(); expect(service.hideAddElementMenu).toHaveBeenCalledWith(true); expect(addMethodSpy).toHaveBeenCalledWith(newCourseContents, testElementUid, testContentUid); }); // 取消场景用例,发射false即可验证取消分支 it('should not add element when confirm modal is canceled', () => { spyOn(service, 'canCourseContentElementBeAdded').and.returnValue(true); spyOn(service, 'hideAddElementMenu').and.callThrough(); const addMethodSpy = spyOn(ADD_COURSE_CONTENT_ELEMENT_MAP, CourseContentElementsMap.ADD_COURSE_CONTENT_ELEMENT_AUDIO).and.callThrough(); const newCourseContents: ICourseContent[] = JSON.parse(JSON.stringify(courseContents)); service.addElementWithConfirmation( course, newCourseContents, newCourseContents[0].uid, `${CourseContentElementType.AUDIO}-${UtilService.generateRandomString(10)}`, 0, true, CourseContentElementsMap.ADD_COURSE_CONTENT_ELEMENT_AUDIO, 'test' ); // 手动触发模态框取消事件,发射false mockModalOnClose.next(false); expect(service.hideAddElementMenu).toHaveBeenCalledWith(true); // 验证添加方法未被调用 expect(addMethodSpy).not.toHaveBeenCalled(); }); });
补充说明
如果用例涉及异步逻辑,可套上fakeAsync+tick或者waitForAsync保证执行时序正确,上述实现默认Subject发射值是同步执行,普通场景足够覆盖。
内容的提问来源于stack exchange,提问作者methuselah
相关产品推荐
相关产品推荐

