You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular组件测试中BsModalService.show方法Spy未被检测到调用的问题求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.17 11:59:45