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()
错误原因
报错核心来自两个问题:
- Capacitor导出的Browser插件对象属性默认是不可配置的,直接调用
spyOn(Browser, 'open')会静默失败,无法替换原始方法为spy对象,最终断言时拿到的还是原始的Function,触发类型报错。 - 测试用例本身存在逻辑和语法错误:
- 大部分用例根本没有调用被测方法
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
相关产品推荐
相关产品推荐

