Как "макет" навигатора .geolocation в React Jest Test

Я пытаюсь написать тесты для созданного мной реактивного компонента, который использует navigator.geolocation.getCurrentPosition() в методе, подобном этому (грубый пример моего компонента):

class App extends Component {

  constructor() {
    ...
  }

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

  render() {
    return(...)
  }

}

Я использую приложение create-реагировать, которое включает в себя тест:

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()
  }
}

Я поместил это в свой тестовый файл для приложения, но это не имело никакого эффекта.

Как я могу "смоделировать" этот метод навигатора и пройти тест?

РЕДАКТИРОВАТЬ: я рассмотрел использование библиотеки под названием геолокации, которая якобы оборачивает navigator.getCurrentPosition для использования в среде узла. Если я правильно понимаю, jest запускает тесты в среде узлов и использует JSDOM для имитации таких вещей, как window. Я не смог найти много информации о поддержке JSDOM navigator. Вышеупомянутая библиотека не работала в моем приложении реакции. Использование специального метода getCurrentPosition вернет только неопределенное значение, даже если сама библиотека была импортирована правильно и доступна в контексте класса App.

Ответ 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

Ответ 2

Я знаю, что эта проблема, возможно, была решена, но кажется, что все вышеупомянутые решения являются неправильными, по крайней мере, для меня.

Когда вы делаете это mock: getCurrentPosition: jest.fn() он возвращает undefined, если вы хотите что-то вернуть, это правильная реализация:

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

Я использую приложение create-реагировать

Ответ 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

В итоге я использовал enzyme. Мой тест теперь выглядит следующим образом:

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

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

Мне пришлось добавить пакеты enzyme и react-addons-test-utils. Конфигурация не требуется. На данный момент я действительно не знаю, почему это зафиксировало проблему, поскольку я не знаком с тем, как работает фермент. Я предполагаю, что это имеет какое-то отношение к этой линии от Enzyme README:

Ферментный API должен быть интуитивно понятным и гибким путем подражания jQuery API для манипуляции и обхода DOM.

Ответ 5

По какой-то причине у меня не был определен объект global.navigator, поэтому мне пришлось указать его в моем файле setupTests.js.

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