如何在 React Jest 测试中“模拟”navigator.geolocation

Posted

技术标签:

【中文标题】如何在 React Jest 测试中“模拟”navigator.geolocation【英文标题】:How to "mock" navigator.geolocation in a React Jest Test 【发布时间】:2017-08-17 22:45:25 【问题描述】:

我正在尝试为我构建的一个反应组件编写测试,该组件在这样的方法中使用navigator.geolocation.getCurrentPosition()(我的组件的粗略示例):

class App extends Component 

  constructor() 
    ...
  

  method() 
    navigator.geolocation.getCurrentPosition((position) => 
       ...code...
    
  

  render() 
    return(...)
  


我正在使用create-react-app,其中包括一个测试:

it('renders without crashing', () => 
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
);

此测试失败,在控制台中打印出来:

TypeError: Cannot read property 'getCurrentPosition' of undefined

我是 React 新手,但对 Angular 1.x 有相当多的经验。在 Angular 中,模拟(在 beforeEach 中的测试中)函数、“服务”和全局对象方法(如 navigator.geolocation.etc)是很常见的。我花时间研究这个问题,这段代码是我能得到的最接近模拟的代码:

global.navigator = 
  geolocation: 
    getCurrentPosition: jest.fn()
  

我把它放在我的 App 测试文件中,但是没有效果。

我怎样才能“模拟”出这个导航器方法并让测试通过?

编辑:我研究了使用一个名为 geolocation 的库,它应该包装 navigator.getCurrentPosition 以在节点环境中使用。如果我理解正确,jest 在节点环境中运行测试并使用 JSDOM 来模拟 window 之类的东西。我还没有找到很多关于 JSDOM 对navigator 的支持的信息。上面提到的库在我的反应应用程序中不起作用。即使库本身已正确导入并且在 App 类的上下文中可用,使用特定方法 getCurrentPosition 只会返回 undefined。

【问题讨论】:

How do I configure jsdom with jest的可能重复 @jordan,你能解释一下为什么你认为它是重复的吗?我查看了该答案并尝试通过使用geolocation 库来解决此问题,该库类似于“节点友好的存根,如'node-localstorage'”。描述的解决方案。但在我的应用程序的上下文中,geolocation.getCurrentPosition 返回未定义,不知道为什么它不起作用。对如何解决这个特定问题的实际解释会更有帮助。 【参考方案1】:

似乎已经有一个 global.navigator 对象,和你一样,我无法重新分配它。

我发现模拟地理定位部分并将其添加到现有的 global.navigator 对我有用。

const mockGeolocation = 
  getCurrentPosition: jest.fn(),
  watchPosition: jest.fn()
;

global.navigator.geolocation = mockGeolocation;

我将其添加到 src/setupTests.js 文件中,如此处所述 - https://create-react-app.dev/docs/running-tests#initializing-test-environment

【讨论】:

当我尝试您的解决方案时出现此错误:'TypeError: Cannot set property 'geolocation' of undefined' 我试图这样定义它:“global.navigator = userAgent:'node.js ' ;"我有这个错误“ReferenceError:未定义初始化”。我不知道该怎么做才能解决这个问题。你知道在我的情况下该怎么做吗? 这个解决方案仍然对我有用。确保在 src/setupTests.js github.com/facebook/create-react-app/blob/master/packages/… 中定义它 最新文档可以在这里找到:facebook.github.io/create-react-app/docs/…【参考方案2】:

我知道这个问题可能已经解决了,但似乎上面所有的解决方案都是错误的,至少对我来说是这样。

当你做这个模拟时:getCurrentPosition: jest.fn() 它返回未定义,如果你想返回一些东西,这是正确的实现:

const mockGeolocation = 
  getCurrentPosition: jest.fn()
    .mockImplementationOnce((success) => Promise.resolve(success(
      coords: 
        latitude: 51.1,
        longitude: 45.3
      
    )))
;
global.navigator.geolocation = mockGeolocation;

我正在使用 create-react-app

【讨论】:

工作就像一个魅力 太棒了!谢谢你!但是你也知道如何触发error函数吗? 对于 TS,它给出了一个错误:“不能分配给 'geolocation' 因为它是一个只读属性。” @GreatQuestion 对于只读限制,我相信这是一个 TypeScript 限制,一个潜在的解决方法是禁用该特定行上的类型。如前所述,这是一个解决方法我得到了我的解决方案来自这个问题:***.com/questions/55712640/…【参考方案3】:

模拟setupFiles

// __mocks__/setup.js

jest.mock('Geolocation', () => 
  return 
    getCurrentPosition: jest.fn(),
    watchPosition: jest.fn(),
  
);

然后在你的package.json

"jest": 
  "preset": "react-native",
  "setupFiles": [
    "./__mocks__/setup.js"
  ]

【讨论】:

【参考方案4】:

我按照上面@madeo 的评论来模拟global.navigator.geolocation。成功了!

另外我做了以下模拟global.navigator.permissions

  global.navigator.permissions = 
    query: jest
      .fn()
      .mockImplementationOnce(() => Promise.resolve( state: 'granted' )),
  ;

根据要求将state 设置为granteddeniedprompt 中的任何一个。

【讨论】:

【参考方案5】:

TypeScript 版本,适用于任何人 Cannot assign to 'geolocation' because it is a read-only property.

mockNavigatorGeolocation.ts 文件中(这可以位于test-utils 文件夹或类似文件夹中)

export const mockNavigatorGeolocation = () => 
  const clearWatchMock = jest.fn();
  const getCurrentPositionMock = jest.fn();
  const watchPositionMock = jest.fn();

  const geolocation = 
    clearWatch: clearWatchMock,
    getCurrentPosition: getCurrentPositionMock,
    watchPosition: watchPositionMock,
  ;

  Object.defineProperty(global.navigator, 'geolocation', 
    value: geolocation,
  );

  return  clearWatchMock, getCurrentPositionMock, watchPositionMock ;
;

然后我将它导入到文件顶部的测试中:

import  mockNavigatorGeolocation  from '../../test-utils';

然后像这样使用函数:

const  getCurrentPositionMock  = mockNavigatorGeolocation();
getCurrentPositionMock.mockImplementation((success, rejected) =>
  rejected(
    code: '',
    message: '',
    PERMISSION_DENIED: '',
    POSITION_UNAVAILABLE: '',
    TIMEOUT: '',
  )
);

【讨论】:

如果我运行npm test,它会抛出一个错误:'TypeError: Cannot redefine property: geolocation at Function.defineProperty ()' 尝试将configurable: true添加到Object.defineProperty中的选项对象【参考方案6】:

出于某种原因,我没有定义 global.navigator 对象,所以我必须在我的 setupTests.js 文件中指定它

const mockGeolocation = 
  getCurrentPosition: jest.fn(),
  watchPosition: jest.fn(),

global.navigator =  geolocation: mockGeolocation 

【讨论】:

以上是关于如何在 React Jest 测试中“模拟”navigator.geolocation的主要内容,如果未能解决你的问题,请参考以下文章

如何在带有 Jest 的 react-native 中使用模拟的 fetch() 对 API 调用进行单元测试

React:如何模拟 Auth0 以使用 Jest 进行测试

当模拟点击调用调用 promise 的函数时,使用 React 的 Jest 和 Enzyme 进行测试

React Jest:如何模拟返回数据的异步 API 调用?

如何使用 jest 模拟 react-router-dom 的 BrowserRouter

使用 Jest 手动模拟 React-Intl 进行快照测试