Angular组件测试中BsModalService.show方法Spy未被检测到调用的问题求助
问题描述
我在测试Angular组件的openTermsAndConditions方法时遇到了一个奇怪的问题:点击页面上的"Terms and Conditions"链接后,控制台已经打印出了方法执行的日志(openTermsAndConditions和done call openTermsAndConditions),说明方法确实被触发了,但测试却断言BsModalService.show被调用失败,错误提示如下:
Expected spy show to have been called. at <Jasmine> at UserContext.apply (http://localhost:9876/_karma_webpack_/webpack:/src/app/public/pages/signup/signup.component.spec.ts:241:33) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/fesm2015/zone.js:368:26) at ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/fesm2015/zone-testing.js:273:39) at _ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/fesm2015/zone.js:367:52)
以下是我的相关代码:
组件方法
public openTermsAndConditions():void { console.log('openTermsAndConditions') this.modalService.show(TermsAndConditionsComponent, { class: 'modal-lg' }); console.log("done call openTermsAndConditions") }
组件HTML模板
<div class="terms-and-conditions-section"> <div class="terms-and-conditions-text"> By clicking on Signup, you agree to the <span data-testid="terms-and-conditions-link" class="link" (click)="openTermsAndConditions()">Terms and Conditions</span> and <span class="link" (click)="openPrivacyPolicy()">Privacy Policy</span> </div> </div>
测试代码
let spectator: Spectator<SignupComponent>; const createComponent = createRoutingFactory({ component: SignupComponent, providers: [ mockProvider(SignupService) ], mocks: [ BsModalService ], imports: [ ModalModule ] }); const createProvider = (signupService: object) => { const providers = { providers: [ mockProvider(SignupService, signupService), ], mocks: [ BsModalService ], imports: [ ModalModule ] }; return providers; }; // Other code here describe('openTermsAndConditions', () => { it('should open the terms and conditions modal when clicked.', () => { spectator = createComponent(createProvider({})); const modalService = spectator.inject(BsModalService); spectator.click(byTestId('terms-and-conditions-link')); expect(modalService.show).toHaveBeenCalled(); }); });
可能的原因与解决思路
1. 异步操作未等待
虽然show方法看起来是同步调用,但ngx-bootstrap的模态框内部可能存在DOM渲染相关的异步逻辑,导致测试在spy被调用前就执行了断言。可以尝试加入异步等待逻辑:
方案一:使用fakeAsync + tick
it('should open the terms and conditions modal when clicked.', fakeAsync(() => { spectator = createComponent(createProvider({})); const modalService = spectator.inject(BsModalService); spectator.click(byTestId('terms-and-conditions-link')); tick(); // 推进虚拟时间,完成所有待处理的异步操作 expect(modalService.show).toHaveBeenCalled(); }));
方案二:使用async/await + whenStable
it('should open the terms and conditions modal when clicked.', async () => { spectator = createComponent(createProvider({})); const modalService = spectator.inject(BsModalService); spectator.click(byTestId('terms-and-conditions-link')); await spectator.fixture.whenStable(); // 等待组件稳定 expect(modalService.show).toHaveBeenCalled(); });
2. 测试配置冲突
你的createComponent是基于createRoutingFactory创建的,而createProvider返回的配置中重复定义了mocks、imports等内容,可能导致配置合并时出现问题,mock的BsModalService没有正确注入到组件中。
可以简化配置逻辑,避免重复定义:
// 调整createProvider,只返回需要覆盖的providers const createProvider = (signupService: object) => { return [ mockProvider(SignupService, signupService), ]; }; // 测试中调用时,只传入额外的providers it('should open the terms and conditions modal when clicked.', () => { spectator = createComponent({ providers: createProvider({}) }); const modalService = spectator.inject(BsModalService); spectator.click(byTestId('terms-and-conditions-link')); expect(modalService.show).toHaveBeenCalled(); });
3. Mock实例不匹配
确保组件中注入的BsModalService和测试中获取的是同一个mock实例。另外,既然已经在createRoutingFactory的mocks数组中声明了BsModalService,就不需要在createProvider中再次重复声明,避免创建多个mock实例。
4. 移除不必要的模块导入
测试中已经mock了BsModalService,真实的ModalModule导入可能会引入真实的服务实例,导致mock失效。可以尝试移除测试配置中的ModalModule导入,看看是否解决问题:
const createComponent = createRoutingFactory({ component: SignupComponent, providers: [ mockProvider(SignupService) ], mocks: [ BsModalService ], // 移除ModalModule导入 });
备注:内容来源于stack exchange,提问作者ReVulV agina

