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

时间:2017-03-24 20:51:15

标签: unit-testing reactjs jestjs create-react-app

我正在尝试为我构建的反应组件编写测试,在这样的方法中使用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的新手,但对于角度1.x有相当多的经验。在angular中,通常模拟(在beforeEach中的测试中)函数,“服务”和全局对象方法,如navigator.geolocation.etc。我花时间研究这个问题,这段代码是我最接近模拟的代码:

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

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

如何“模拟”这个导航器方法并让测试通过?

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

7 个答案:

答案 0 :(得分:18)

似乎已经存在global.navigator个对象,并且和您一样,我无法重新分配它。

我发现嘲笑地理定位部分并将其添加到现有global.navigator为我工作。

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

global.navigator.geolocation = mockGeolocation;

我将此添加到src/setupTests.js文件中,如此处所述 - https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#initializing-test-environment

答案 1 :(得分:8)

我知道这个问题可能已经解决,但似乎所有上述解决方案都是错误的,至少对我而言。

进行此模拟时: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

答案 2 :(得分:4)

模拟setupFiles

// __mocks__/setup.js

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

然后在package.json

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

答案 3 :(得分:1)

我按照@madeo的评论嘲笑了global.navigator.geolocation。奏效了!

此外,我还模拟了global.navigator.permissions

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

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

答案 4 :(得分:1)

任何人的 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: '',
  })
);

答案 5 :(得分:0)

我最终使用enzyme。我的测试现在看起来像:

import React from 'react'
import App from './App'
import {shallow} from 'enzyme'

it('renders without crashing', () => {
  const app = shallow(<App />)
  expect(app).toBeDefined()
});

我必须添加enzymereact-addons-test-utils个包。无需配置。截至目前,我实际上并不知道为什么这样可以解决问题,因为我不熟悉酶的工作原理。我想这与Enzyme的README中的这一行有关:

  

Enzyme的API通过模仿来实现直观和灵活   用于DOM操作和遍历的jQuery API。

答案 6 :(得分:0)

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

const mockGeolocation = {
  getCurrentPosition: jest.fn(),
  watchPosition: jest.fn(),
}
global.navigator = { geolocation: mockGeolocation }