类型错误:_mapboxGl.default.Map 不是构造函数

Sze*_*lek 6 mocking reactjs jestjs mapbox-gl enzyme

尝试为 react.js 组件编写单元测试。该组件实现了使用 mapbox API 呈现的地图。但不幸的是我遇到了一系列问题:

第一个是:TypeError: window.URL.createObjectURL is not a function 在此处输入图片说明

由于这个,我已经解决了这个问题:https : //github.com/mapbox/mapbox-gl-js/issues/3436,通过添加以下代码:

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
    Map: () => ({})
}))
Run Code Online (Sandbox Code Playgroud)

然后第二个是:ReferenceError:shallow is not defined 在此处输入图片说明

为了解决这个问题的基础:酶导入的参考错误

1) npm istall 酶,

2)添加行:

import { shallow, render, mount } from 'enzyme'
Run Code Online (Sandbox Code Playgroud)

第三个问题是:从'enzyme-adapter-react-15'导入适配器 在此处输入图片说明

感谢这篇文章:找不到酶适配器反应 16 的声明文件?,下一步是添加以下代码:

import Adapter from 'enzyme-adapter-react-16'
import enzyme from 'enzyme'

enzyme.configure({ adapter: new Adapter() })
Run Code Online (Sandbox Code Playgroud)

现在终于有了:TypeError: _mapboxGl.default.Map is not a constructor 在此处输入图片说明

现在,不幸的是,我在网上找不到有意义的解决方案。

有没有人有类似的问题?

为什么对 mapbox API 进行单元测试如此困难?

也许我做错了,整个解决方案是垃圾?如果是这样,任何人都可以提出替代方案吗?

在整个测试代码下面:

import React, { Component } from 'react'
import { shallow, render, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import enzyme from 'enzyme'

enzyme.configure({ adapter: new Adapter() })

import Map from '../Map'

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
    Map: () => ({})
}))

describe('<Map />', ()=>{

    let mapWrapper
    let mapInstance

    const map = (disableLifecycleMethods = false)=>shallow(<Map />,{disableLifecycleMethods})

    beforeEach(()=>{
        mapWrapper = map()
        mapInstance = mapWrapper.instance()
    })

    afterEach(() => {
        mapWrapper = undefined;
        mapInstance = undefined;
    })

    it('renders without crashing', () => {
        expect(map().exists()).toBe(true);
    })
})
Run Code Online (Sandbox Code Playgroud)

在测试的组件代码下方:

import React, { Component } from 'react'
import mapboxgl from 'mapbox-gl'

//Mechanics
import {importContours} from './utilities/importContours'
import {addData} from './utilities/addData'
import {setLegend} from './utilities/setLegend'

//Components
import Searchbar from '../search/Searchbar'
import Tabbar from '../tabbar/Tabbar'
import Legend from '../legend/Legend'
//import Popup from '../popup/Popup'

class Map extends Component {

    map

    constructor(){
        super()
        this.state = {
            active: null,
            fetchData: null,
            mapType: 0,
            searchedPhrase: ''
        }
    }

    componentDidUpdate() {
        this.setMapLayer()          
    }

    componentDidMount() {

        mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN
        this.map = new mapboxgl.Map({
            container: 'Map',
            style: 'mapbox://styles/mapbox/streets-v9',
            center: [16.145136, 51.919437],
            maxZoom: 13,
            minZoom: 3,
            zoom: 5.7,
        })

        this.map.once('load', () => {}) 
    }

    setMapLayer(){

        if (!this.map.loaded() || this.state.searchedPhrase === '') return

        var contours = importContours(this.state.mapType)
        var contoursWithData = addData(contours, this.state.mapType, this.state.searchedPhrase)
        contoursWithData.then((data)=>{
            var mpSource = this.map.getSource("contours")

            if (typeof mpSource === 'undefined') 
                this.map.addSource('contours', { type: 'geojson', data })
            else 
                this.map.getSource("contours").setData(data)

            var mpLayer = this.map.getLayer("contours")

            if (typeof mpLayer === 'undefined') {
                this.map.addLayer({
                    id: 'contours',
                    type: 'fill',
                    source: 'contours',
                    layout: {},
                    paint: {
                        'fill-opacity': [
                            'case',
                            ['boolean', ['feature-state', 'hover'], false],
                            0.8,
                            0.4
                        ]
                    }
                }, 'country-label-lg')

                this.map.addLayer({
                    id: 'state-borders',
                    type: 'line',
                    source: 'contours',
                    layout: {},
                    paint: {
                        'line-color': '#c44cc0',
                        'line-width': 0.01
                    }
                })
            }

            var hoveredStateId = null

            // When the user moves their mouse over the state-fill layer, we'll update the
            // feature state for the feature under the mouse.
            this.map.on('mousemove', 'contours', (e) => {
                if (e.features.length > 0) {
                    if (hoveredStateId) {
                        this.map.setFeatureState(
                            { source: 'contours', id: hoveredStateId },
                            { hover: false }
                        )
                    }

                    hoveredStateId = e.features[0].id
                    this.map.setFeatureState(
                        { source: 'contours', id: hoveredStateId },
                        { hover: true }
                    )
                }
            })

            // When the mouse leaves the state-fill layer, update the feature state of the
            // previously hovered feature.
            this.map.on('mouseleave', 'contours', () => {
                if (hoveredStateId) {
                    this.map.setFeatureState(
                        { source: 'contours', id: hoveredStateId },
                        { hover: false }
                    )
                }
                hoveredStateId = null
            }) 

            // When the user click their mouse over the layer, we'll update the
            this.map.on('click', 'contours', (e) => {

                var popupHTML = `<Popover 
                    style = { zIndex: 2, position: 'absolute' }
                    anchorOrigin={{ vertical: 'center',horizontal: 'center'}}
                    transformOrigin={{vertical: 'center',horizontal: 'center'}}
                >
                    ${e.features[0].id}
                </Popover>`

                if (e.features.length > 0) {
                    new mapboxgl.Popup(
                        {style:"zIndex: 2"},
                        {closeButton: false, closeOnClick: true}
                        )
                    .setLngLat(e.lngLat)
                    .setHTML(popupHTML)
                    .addTo(this.map);
                }
            })

            this.setState({
                active: setLegend(data)
            })

            //Set fill
            if(this.state.active == null) return 

            const { property, stops } = this.state.active

            this.map.setPaintProperty('contours', 'fill-color', {
              property,
              stops
            })
        })
    }

    handleChange = (newMapType) => {

        if (this.state.mapType === newMapType) return

        const { searchedPhrase } = this.state

        if (typeof searchedPhrase === 'undefined')return

        this.setState({mapType:newMapType})
    }

    handleSearch = (newSearchPhrase) => {

        if (typeof newSearchPhrase === 'undefined') return

        this.setState({searchedPhrase:newSearchPhrase.toUpperCase()})    
    }

    render(){
        return (
            <div id="Map">
                <Searchbar click={this.handleSearch.bind(this)}/>
                <Tabbar click={this.handleChange.bind(this)}/>
                <Legend active={this.state.active}/>
            </div>
        )
    }
}

export default Map
Run Code Online (Sandbox Code Playgroud)

Mor*_*kop 2

您可以将下面的代码添加到我的测试条目文件中src/setupTests.ts

jest.mock('mapbox-gl/dist/mapbox-gl', () => ({
  GeolocateControl: jest.fn(),
  Map: jest.fn(() => ({
    addControl: jest.fn(),
    on: jest.fn(),
    remove: jest.fn(),
  })),
  NavigationControl: jest.fn(),
}));
Run Code Online (Sandbox Code Playgroud)