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

Angular测试Capacitor Browser遇Expected a spy but got Function错误

问题现象

编写Angular单元测试验证Capacitor Browser插件调用逻辑时,出现如下报错:

Error: <toHaveBeenCalled> : Expected a spy, but got Function.

组件内被测方法代码:

async goToPage(path: string) {
    let url_ = environment.apiUrl + path;
    await Browser.open({url : url_,presentationStyle: 'popover'})
}

先后尝试多种spy写法均未解决,失败用例如下:

it('should use In App Browser', () => {
    const browser = Browser;
    const spy = spyOn(browser,'open');
    const url = '/api/resource/TOS';
    component.goToPage(url);
    expect(spy.call).toHaveBeenCalled();
}); 

it('should use In App Browser', () => {
    spyOn(Browser,'open');
    expect(Browser.open).toHaveBeenCalled();
});
//Error: <toHaveBeenCalled> : Expected a spy, but got Function.


it('should use In App Browser', () => {
    const browser = Browser;
    spyOn(browser,'open');
    expect(browser.open).toHaveBeenCalled();
});
//Expected a spy, but got Function.


it('should use In App Browser', () => {
    const browser = Browser;
    spyOn(browser,'open').and.returnValue(Promise.resolve());;
    expect(browser.open).toHaveBeenCalled();
});
//Expected a spy, but got Function.


it('should use In App Browser', async() => {
    const browser = Browser;
    spyOn(browser, 'open')

    expect(browser.open).toHaveBeenCalled();
});
//Error: <toHaveBeenCalled> : Expected a spy, but got Function.
//Usage: expect(<spyObj>).toHaveBeenCalled()


it('should use In App Browser', async() => {
  const browser = Browser;
  spyOn(browser, 'open')
  expect(await browser.open).toHaveBeenCalled();
});
//Error: <toHaveBeenCalled> : Expected a spy, but got Function.


it('should create', (done) => {
  let browser = Browser;
  spyOn<any>(browser, 'open');
  const url_ = environment.apiUrl + '/api/resource/About';
  fixture.whenStable().then(() => {
      expect(browser.open).toHaveBeenCalled();   expect(browser.open).toHaveBeenCalledWith({url : url_,presentationStyle: 'popover'});
      done();
  });
});
//Unhandled promise rejection: Error: <toHaveBeenCalled> : Expected a spy, but got Function.
//Usage: expect(<spyObj>).toHaveBeenCalled()
错误原因

报错核心来自两个问题:

  1. Capacitor导出的Browser插件对象属性默认是不可配置的,直接调用spyOn(Browser, 'open')会静默失败,无法替换原始方法为spy对象,最终断言时拿到的还是原始的Function,触发类型报错。
  2. 测试用例本身存在逻辑和语法错误:
    • 大部分用例根本没有调用被测方法component.goToPage(),直接断言方法被调用,逻辑不成立
    • 存在语法错误:比如expect(spy.call)、expect(await browser.open),toHaveBeenCalled匹配器要求直接传入spy对象本身
    • 没有等待async异步方法执行完成就触发断言,存在时序问题
修复方案

不要直接对原始导入的Browser对象调用spyOn,先通过属性重定义的方式把open方法替换为可控的spy,调用被测方法时await等待异步执行完成,再做断言,正确代码如下:

import { Browser } from '@capacitor/browser';
import { environment } from 'src/environments/environment';

it('should call Browser.open with correct params when goToPage is triggered', async () => {
  // 创建测试spy,强制替换Browser.open为可写的spy对象
  const browserOpenSpy = jasmine.createSpy('browserOpen').and.resolveTo();
  Object.defineProperty(Browser, 'open', {
    value: browserOpenSpy,
    writable: true,
    configurable: true
  });

  const testPath = '/api/resource/TOS';
  const expectedFullUrl = environment.apiUrl + testPath;

  // 调用被测方法,等待异步逻辑执行完成
  await component.goToPage(testPath);

  // 校验调用结果
  expect(browserOpenSpy).toHaveBeenCalledTimes(1);
  expect(browserOpenSpy).toHaveBeenCalledWith({
    url: expectedFullUrl,
    presentationStyle: 'popover'
  });
});

补充:更好的实践是不直接在组件中硬依赖Capacitor的Browser静态类,而是通过Angular依赖注入的方式封装Browser服务,测试时直接替换服务为Mock桩,从根源避免静态类打桩的问题。

内容的提问来源于stack exchange,提问作者Fredy Vzq

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 01:57:24