React Native 上的 Google 地图、地理定位和单元测试
在本教程中,我们将使用 React Native CLI 构建一个适用于 iOS 和 Android 的出租车应用。此外,我们还将深入使用 Jest + React 测试库对整个应用进行单元测试。
我还有一个视频版本,这是本教程的专业功能。快来看看吧🚨🚀👉视频版本
环境设置
首先,请确保在开始之前已准备好开发环境。我使用的是一台 macOS 笔记本电脑,并配备了两部 iPhone:iPhone 12 和 iPhone SE 2020。虽然测试 App 并不需要真机,但如果没有 Android/iPhone,也可以使用模拟器。不过,我们建议您在真机上测试 App。
我使用的是 React Native 0.64 版本,请确保你也使用相同的版本,以避免在使用 react-native-maps 等主要库时出现兼容性问题。我会尽量使本教程与 React Native CLI 的主要版本保持同步。
点击此链接安装本地环境。开始吧!
创建应用程序
让我们使用 npx 命令创建应用程序
npx react-native init taxiApp --version 0.64.2
创建一个src文件夹,并将 App.js 文件移动到该位置。最终,你应该得到./src/App.js。我们这个项目不使用 TypeScript (TS),因此请删除所有与 TS 相关的代码,并将 TypeScript App 函数转换为常规 JavaScript 函数。
import React from "react"
import { SafeAreaView, StatusBar, StyleSheet, Text, View } from "react-native"
const App = () => {
return (
<SafeAreaView>
<StatusBar barStyle="dark-content" />
<View style={styles.sectionContainer}>
<Text style={styles.sectionTitle}>Welcome to Taxi App</Text>
</View>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: "600",
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: "400",
},
})
export default App
我已经使用来自 React Native 的StyleSheet对象放置了一个临时的“欢迎使用出租车应用程序”消息,其中包含一些样式,这很好,但这只是暂时的,因为我们很快就会进入样式组件。
因为我将App.js移到了src文件夹中,所以我们必须更新App.js以在主index.js中导入新位置。
从
import App from "./App"
到
import App from "./src/App"
在模拟器中运行
首先我们需要启动 Metro。
要启动 Metro,请在 React Native 项目文件夹中运行npx react-native start :
npx react-native start
让 Metro Bundler 在它自己的终端中运行。在 React Native 项目文件夹中打开一个新终端。运行以下命令:
npx react-native run-ios
如果一切设置正确,您应该很快就会看到您的新应用程序在您的 iPhone 模拟器中运行。
对于 Android,首先运行 Android 模拟器,然后运行以下命令:
npx react-native run-android
安装 React Native Maps
React Native 中用于处理 Maps 的默认库是react-native-maps,接下来我们将使用它。
npm install react-native-maps --save-exact
我们将使用 Google 地图而不是 Apple 地图,因为 Apple 地图仅适用于 iOS 设备。要继续,我们需要Android SDK和iOS SDK的 API 密钥。您需要创建一个Google 结算账户,因此请点击链接并创建一个账户。
Google 结算帐户
创建 Google Cloud 帐户后,您需要创建一个新项目。在新项目中,我们将创建用于访问Android 版 Maps SDK、iOS 版 Maps SDK等的API 和服务。
- 在console.cloud.google.com内创建新项目
- 单击 API 和服务 > 凭证。
- 点击“+ 创建凭据”。它会立即生成一个 API 密钥。我们将用这个密钥在 iOS 和 Android 设备上设置 Google 地图。
- 点击最近创建的凭据来限制密钥。搜索“API 限制”部分。您将看到一个“限制密钥”选项。请确保选择“Maps SDK for Android”和“Maps SDK for iOS”。
- 点击“保存”
在 iOS 上构建配置
设置使用描述属性
应用程序的Info.plist文件必须包含一个NSLocationWhenInUseUsageDescription,其中包含面向用户的目的字符串,清楚完整地解释您的应用程序为什么需要该位置,否则 Apple 将拒绝您的应用程序提交。
在你的./ios/taxiApp/Info.plist中,确保你有这个:
...
<key>NSLocationWhenInUseUsageDescription</key>
<string>In order to work we need you to grant location access</string>
...
在 iOS 中启用 Google 地图
如果您想在 iOS 上启用 Google 地图,请复制 Google API 密钥并按如下方式编辑您的./ios/taxiApp/AppDelegate.m :
+ #import <GoogleMaps/GoogleMaps.h>
@implementation AppDelegate
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
+ [GMSServices provideAPIKey:@"_YOUR_API_KEY_"]; // add this line using the api key obtained from Google Console
...
[GMSServices provideAPIKey] 应该是该方法的第一次调用。
将以下内容添加到Podfile中的config = use_native_modules!上方:
# React Native Maps dependencies
rn_maps_path = '../node_modules/react-native-maps'
pod 'react-native-google-maps', :path => rn_maps_path
pod 'GoogleMaps'
pod 'Google-Maps-iOS-Utils'
现在,我们将使用 CocoaPods 为 iOS 构建应用。安装 npm 包后,我们需要安装 pod。
npx pod-install
在 Android 上构建配置
配置 Google Play 服务。在android/build.gradle中添加以下两行:
ext {
buildToolsVersion = "29.0.3"
minSdkVersion = 21
compileSdkVersion = 29
targetSdkVersion = 29
ndkVersion = "20.1.5948944"
playServicesVersion = "17.0.0" // <= 👈
androidMapsUtilsVersion = "2.2.0" // <= 👈
}
指定你的 Google 地图 API 密钥。将你的 API 密钥添加到清单文件 (android/app/src/main/AndroidManifest.xml) 中:
<application>
<!-- You will only need to add this meta-data tag, but make sure it's a child of application -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="Your Google maps API Key Here"/>
<!-- You will also only need to add this uses-library tag -->
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application>
开始使用 React Native Maps
打开 App.js 文件,并将内容替换为以下代码。目的是让 Google 地图覆盖设备的整个屏幕。为此,我们添加了新的样式:
import React from "react"
import { SafeAreaView, StatusBar, StyleSheet } from "react-native"
import MapView, { PROVIDER_GOOGLE } from "react-native-maps"
const App = () => {
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 57.709127,
longitude: 11.934746,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
alignItems: "center",
},
map: {
...StyleSheet.absoluteFillObject,
},
})
export default App
如果您在模拟器上运行该应用程序,您应该会看到类似这样的内容:
显示用户位置
我们将请求用户授予位置权限,以便在地图上显示用户的位置。为此,我们将使用react-native-permissions包。您可以按照此处的说明,了解如何在 iOS 和 Android 上进行设置。
npm install --save-exact react-native-permissions@3.0.1
对于 iOS 设置,您必须打开ios 文件夹内的Podfile并添加下一行代码:
# React Native Permissions
permissions_path = '../node_modules/react-native-permissions/ios'
pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse"
之前我们已经在 Info.plist 中添加了以下几行,但您可以仔细检查一下:
<key>NSLocationWhenInUseUsageDescription</key>
<string>In order to work we need you to grant location access</string>
现在使用 Cocoa Pods 安装依赖项以完成 ios 的进程。
npx pod-install
对于 Android,您只需更新android/app/src/main/AndroidManifest.xml文件。
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
现在,让我们使用 React Native 权限来显示用户位置。在 App.js 文件中添加以下几行:
import React, { useEffect } from "react"
import { SafeAreaView, StatusBar, StyleSheet } from "react-native"
import MapView, { PROVIDER_GOOGLE } from "react-native-maps"
import { check, request, PERMISSIONS, RESULTS } from "react-native-permissions" // 👈
const App = () => {
const handleLocationPermission = async () => { // 👈
let permissionCheck = '';
if (Platform.OS === 'ios') {
permissionCheck = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
if (
permissionCheck === RESULTS.BLOCKED ||
permissionCheck === RESULTS.DENIED
) {
const permissionRequest = await request(
PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
);
permissionRequest === RESULTS.GRANTED
? console.warn('Location permission granted.')
: console.warn('location permission denied.');
}
}
if (Platform.OS === 'android') {
permissionCheck = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION);
if (
permissionCheck === RESULTS.BLOCKED ||
permissionCheck === RESULTS.DENIED
) {
const permissionRequest = await request(
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
);
permissionRequest === RESULTS.GRANTED
? console.warn('Location permission granted.')
: console.warn('location permission denied.');
}
}
};
useEffect(() => {
handleLocationPermission()
}, [])
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: 57.709127,
longitude: 11.934746,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
showsUserLocation={true} // 👈
/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
alignItems: "center",
},
map: {
...StyleSheet.absoluteFillObject,
},
})
export default App
如果您使用 iOS 模拟器运行该应用程序,您将看到一个弹出窗口,要求您批准权限。
确认后,您应该会看到一个蓝色圆圈,指示您当前的位置。如果没有,可能是因为您的模拟器没有使用自定义位置,您需要更新它。为此,请进入模拟器菜单并执行以下操作:
- 点击功能>位置>自定义位置
- 输入此位置:纬度:57,705871 & 经度:11,938823
这个位置是基于地图initialRegion的,在上面的代码中它:
initialRegion={{
latitude: 57.709127,
longitude: 11.934746,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
您可以看到我的自定义位置位于我的初始地图区域内,即瑞典哥德堡市。
追踪用户位置
之前我们只显示用户的当前位置,但当用户改变位置时,我们需要持续跟踪用户位置。为此,我们将使用react-native-geolocation-service。
npm install react-native-geolocation-service@5.2.0 --save-exact
设置
您可以参考他们文档中更详细的指南。我们已经为 iOS 和 Android 设置了位置权限。对于 iOS,我们将运行:
npx pod-install
让我们更新我们的 App.js 组件以使用地理位置跟踪用户位置。
import React, { useEffect, useState } from "react"
import { SafeAreaView, StatusBar, StyleSheet } from "react-native"
import MapView, { PROVIDER_GOOGLE } from "react-native-maps"
import { check, request, PERMISSIONS, RESULTS } from "react-native-permissions"
import Geolocation from "react-native-geolocation-service" // 👈
const App = () => {
const [location, setLocation] = useState(null) // 👈
const handleLocationPermission = async () => {
let permissionCheck = ""
if (Platform.OS === "ios") {
permissionCheck = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE)
if (permissionCheck === RESULTS.DENIED) {
const permissionRequest = await request(
PERMISSIONS.IOS.LOCATION_WHEN_IN_USE
)
permissionRequest === RESULTS.GRANTED
? console.warn("Location permission granted.")
: console.warn("Location perrmission denied.")
}
}
if (Platform.OS === "android") {
permissionCheck = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION)
if (permissionCheck === RESULTS.DENIED) {
const permissionRequest = await request(
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
)
permissionRequest === RESULTS.GRANTED
? console.warn("Location permission granted.")
: console.warn("Location perrmission denied.")
}
}
}
useEffect(() => {
handleLocationPermission()
}, [])
useEffect(() => { // 👈
Geolocation.getCurrentPosition(
position => {
const { latitude, longitude } = position.coords
setLocation({ latitude, longitude })
},
error => {
console.log(error.code, error.message)
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
)
}, [])
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
{location && ( // 👈
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: location.latitude, // 👈
longitude: location.longitude,// 👈
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
showsUserLocation={true}
/>
)}
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
alignItems: "center",
},
map: {
...StyleSheet.absoluteFillObject,
},
})
export default App
继续。
- 使用 useState 添加本地状态
const [location, setLocation] = useState(null)
。 - 当组件安装时使用 useEffect 我们调用地理位置服务并更新位置状态。
<MapView />
添加了一个条件,仅当位置状态不为空时才显示组件。
向 MapView 添加自定义样式和属性
我们可以更改 Google 地图的颜色和整体外观。此外,MapView 组件附带一些有用的属性,我们将添加其中几个,但您可以在此处找到完整列表。
让我们在 src 文件夹中创建一个名为 style 的新文件夹;这将是我们将添加更多内容的通用样式位置
(./src/styles/index.js)
export const customStyleMap = [
{
elementType: "geometry",
stylers: [
{
color: "#242f3e",
},
],
},
{
elementType: "labels.text.fill",
stylers: [
{
color: "#746855",
},
],
},
{
elementType: "labels.text.stroke",
stylers: [
{
color: "#242f3e",
},
],
},
{
featureType: "administrative.locality",
elementType: "labels.text.fill",
stylers: [
{
color: "#d59563",
},
],
},
{
featureType: "poi",
elementType: "labels.text.fill",
stylers: [
{
color: "#d59563",
},
],
},
{
featureType: "poi.park",
elementType: "geometry",
stylers: [
{
color: "#263c3f",
},
],
},
{
featureType: "poi.park",
elementType: "labels.text.fill",
stylers: [
{
color: "#6b9a76",
},
],
},
{
featureType: "road",
elementType: "geometry",
stylers: [
{
color: "#38414e",
},
],
},
{
featureType: "road",
elementType: "geometry.stroke",
stylers: [
{
color: "#212a37",
},
],
},
{
featureType: "road",
elementType: "labels.text.fill",
stylers: [
{
color: "#9ca5b3",
},
],
},
{
featureType: "road.highway",
elementType: "geometry",
stylers: [
{
color: "#746855",
},
],
},
{
featureType: "road.highway",
elementType: "geometry.stroke",
stylers: [
{
color: "#1f2835",
},
],
},
{
featureType: "road.highway",
elementType: "labels.text.fill",
stylers: [
{
color: "#f3d19c",
},
],
},
{
featureType: "transit",
elementType: "geometry",
stylers: [
{
color: "#2f3948",
},
],
},
{
featureType: "transit.station",
elementType: "labels.text.fill",
stylers: [
{
color: "#d59563",
},
],
},
{
featureType: "water",
elementType: "geometry",
stylers: [
{
color: "#17263c",
},
],
},
{
featureType: "water",
elementType: "labels.text.fill",
stylers: [
{
color: "#515c6d",
},
],
},
{
featureType: "water",
elementType: "labels.text.stroke",
stylers: [
{
color: "#17263c",
},
],
},
]
现在,我们在 src 目录下创建更多文件夹。接下来是 screens 文件夹,我们将在其中创建我们的第一个屏幕文件,名为 UserScreen.js。在 UserScreen 文件夹中,我们将移动 App.js 的内容。( ./src/screens/UserScreen.js )
/**
1. Copy and paste code from App.js
2. Rename component name from App to UserScreen
*/
import React, { useEffect, useState } from "react"
import { SafeAreaView, StatusBar, StyleSheet } from "react-native"
import MapView, { PROVIDER_GOOGLE } from "react-native-maps"
import { check, request, PERMISSIONS, RESULTS } from "react-native-permissions"
import Geolocation from "react-native-geolocation-service"
const UserScreen = () => {
const [location, setLocation] = useState(null)
const handleLocationPermission = async () => {
let permissionCheck = ""
if (Platform.OS === "ios") {
permissionCheck = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE)
if (permissionCheck === RESULTS.DENIED) {
const permissionRequest = await request(
PERMISSIONS.IOS.LOCATION_WHEN_IN_USE
)
permissionRequest === RESULTS.GRANTED
? console.warn("Location permission granted.")
: console.warn("Location perrmission denied.")
}
}
if (Platform.OS === "android") {
permissionCheck = await check(PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION)
if (permissionCheck === RESULTS.DENIED) {
const permissionRequest = await request(
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
)
permissionRequest === RESULTS.GRANTED
? console.warn("Location permission granted.")
: console.warn("Location perrmission denied.")
}
}
}
useEffect(() => {
handleLocationPermission()
}, [])
useEffect(() => {
Geolocation.getCurrentPosition(
position => {
const { latitude, longitude } = position.coords
setLocation({ latitude, longitude })
},
error => {
console.log(error.code, error.message)
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
)
}, [])
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
{location && (
<MapView
style={styles.map}
provider={PROVIDER_GOOGLE}
initialRegion={{
latitude: location.latitude,
longitude: location.longitude,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
showsUserLocation={true}
/>
)}
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
alignItems: "center",
},
map: {
...StyleSheet.absoluteFillObject,
},
})
export default UserScreen
之后,我们的 App.js 组件将不再包含上述代码。相反,我们将导入该<UserScreen />
组件并进行渲染。我们将在 App.js 中使用 React Navigation 来处理应用程序的所有屏幕。
/**
For now just import and render <UserScreen />
*/
import React from "react"
import UserScreen from "./screens/UserScreen"
const App = () => {
return <UserScreen />
}
export default App
我们的文件夹目录应该是这样的。
最后,让我们将我们的 customMapStyle 和其他道具运用到 UserScreen.js 上的 MapView 中
...
import {customStyleMap} from '../styles'; // 👈
const UserScreen = () => {
...
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
{location && (
<MapView
...
customMapStyle={customStyleMap} // 👈
paddingAdjustmentBehavior="automatic" // 👈
showsMyLocationButton={true} // 👈
showsBuildings={true} // 👈
maxZoomLevel={17.5} // 👈
loadingEnabled={true} // 👈
loadingIndicatorColor="#fcb103" // 👈
loadingBackgroundColor="#242f3e" // 👈
/>
)}
</SafeAreaView>
);
};
...
export default UserScreen;
我们的地图焕然一新!😍
单元测试
测试时间到了!😍
我们将深入研究该<UserScreen />
组件的第一个测试用例,我们将使用:
- 笑话
- React 测试库 Native 📚
因此,让我们安装 React Testing Library Native
npm install --save-dev @testing-library/react-native
另外,让我们安装额外的 jest 匹配器:
npm install --save-dev @testing-library/jest-native
然后使用Jest 配置中的setupFilesAfterEnv选项自动将其添加到您的 jest 测试中(它通常位于 package.json 中的“jest”键下或 jest.config.json 文件中):
// package.json
"jest": {
"preset": "react-native",
// 👇
"setupFilesAfterEnv": [
"@testing-library/jest-native/extend-expect"
]
}
现在,我们需要用到一些库,例如Geolocation、react-native-permissions和MapView组件,并创建这些组件/库的模拟。为此,我们在应用的根目录中创建一个名为jest-setup.js 的文件。目前,我们只导入 jest。
// ./jest-setup.js
import { jest } from "@jest/globals"
好了,好了,让我们为<UserScreen />
组件创建第一个测试。为此,在src/screens中,创建__tests__
(双下划线,两侧) 文件夹。在其中创建名为UserScreen.test.js的文件。
// ./src/screens/__tests__/UserScreen.test.js
import React from "react"
import { render, waitFor } from "@testing-library/react-native"
import UserScreen from "../UserScreen"
describe("<UserScreen />", () => {
test("should renders MapView and Marker with user current location", () => {
render(<UserScreen />)
})
})
现在,如果我们尝试运行package.json 文件中已有的测试命令,会发生什么?
npm run test
运行 test 命令后,你会注意到Jest尝试运行我们已有的两个测试文件。该命令将运行我们在应用中定义的所有测试文件。我们默认已经有一个测试,它来自 App.js 文件。第二个测试就是我们上面编写的那个。
另外,你会看到测试失败了!😱
测试失败了,这完全正常。您可以看到,问题在于 Jest 尝试从react-native-maps库中为 UserScreen.test.js 文件导入MapView组件,但失败了。这就是为什么我们需要模拟 react-native-maps 以使测试能够通过。
那就行动起来吧!💪
打开jest-setup.js文件并模拟 react-native-maps。
jest.mock("react-native-maps", () => {
const React = require("react")
const { View } = require("react-native")
class MockMapView extends React.Component {
render() {
const { testID, children, ...props } = this.props
return (
<View
{...{
...props,
testID,
}}
>
{children}
</View>
)
}
}
const mockMapTypes = {
STANDARD: 0,
SATELLITE: 1,
HYBRID: 2,
TERRAIN: 3,
NONE: 4,
MUTEDSTANDARD: 5,
}
return {
__esModule: true,
default: MockMapView,
MAP_TYPES: mockMapTypes,
PROVIDER_DEFAULT: "default",
PROVIDER_GOOGLE: "google",
}
})
我们为 react-native-maps 创建了一个模拟组件。我们使用了 React Class 组件,主要是因为我在使用函数组件时遇到了问题。或许你可以尝试用函数组件来代替类组件。我们获取 MapView 组件可能拥有的所有 props,以及作为子组件传递的所有内容。最后,我们返回 MockMapView 作为默认导出,因为当我们从 react-native-maps 导入 MapView 时,你可以看到它是一个默认导出。
接下来,我们需要告诉 Jest 我们有一个用于测试的setupFiles 文件。我们在 package.json 文件的 jest 部分中执行此操作。
"jest": {
"preset": "react-native",
"setupFilesAfterEnv": [
"@testing-library/jest-native/extend-expect"
],
// 👇
"setupFiles": [
"./jest-setup.js"
]
}
尝试再次运行测试命令
npm run test
然后...失败了!
这次至少在 MapView 上没有失败。这次失败是因为react-native-permissions。因为我们还没有 mock 这个。
那就行动起来吧!💪
返回jest-setup.js并添加以下内容:
jest.mock("react-native-permissions", () =>
require("react-native-permissions/mock")
)
如果由于某些原因你仍然遇到问题,export {PERMISSIONS, RESULT}
那么你可以尝试将transformIgnorePatterns添加到 package.json 中的 Jest 配置中
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native-permissions|)" // See I added react-native-permissions
],
我们还缺少最后一个模拟,那就是地理位置。与其在 jest-setup.js 文件中模拟,不如__mocks__
在项目根目录创建一个文件夹。在 mocks 文件夹中,添加库的名称react-native-geolocation-service.js。它必须与库的名称相同。
// ./__mocks__/react-native-geolocation-service.js
export default {
getCurrentPosition: jest.fn().mockImplementation(successCallback => {
const position = {
coords: {
latitude: 57.7,
longitude: 11.93,
},
}
successCallback(position)
}),
}
呼,我想我们已经完成了从外部包模拟库/组件的工作。是时候重新运行测试了,但我们可以删除该__tests__/App.test.js
文件。我们暂时不测试 App.js。我们专注于src/screens/__tests__/UserScreen.test.js
……
npm run test
并且...它应该通过!!
我们仅测试了 UserScreen 组件是否渲染。接下来,让我们测试一下 Map 组件是否渲染并调用位置权限来提升代码覆盖率。
// src/screens/__tests__/UserScreen.test.js
import React from "react"
import { render, waitFor } from "@testing-library/react-native"
import UserScreen from "../UserScreen"
// Import check from react-native-permissions
import { check } from "react-native-permissions"
// Import Geolocation also
import Geolocation from "react-native-geolocation-service"
describe("<UserScreen />", () => {
test("should renders MapView and Marker with user current location", async () => {
render(<UserScreen />)
await waitFor(() => {
expect(check).toHaveBeenCalledTimes(1)
expect(Geolocation.getCurrentPosition).toHaveBeenCalledTimes(1)
})
})
})
我们可以安全地从模拟的 react-native-permissions 库中导入check函数。Geolocation也一样。我们使用 React Testing Library Native 中的 async/await 和waitFor,因为组件挂载时,我们首先检查权限。其次,我们调用当前用户位置。然后我们使用 setLocation({latitude, longitude}) 更新状态。所以,有几件事正在进行,我们必须等待这些操作完成。
让我们在 MapView 组件中添加一个 testID,以确保地图能够被渲染。打开 UserScreen 组件并添加一个 testID。
...
const UserScreen = () => {
...
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" />
{location && (
<MapView
testID="map" // 👈
...
/>
)}
</SafeAreaView>
);
};
...
我们还需要将新的 testID 传递给我们模拟的 MapView 组件。因此,打开 jest-setup.js 文件并添加以下内容:
jest.mock('react-native-maps', () => {
const React = require('react');
const {View} = require('react-native');
class MockMapView extends React.Component {
render() {
const {testID, children, ...props} = this.props; // 👈
return (
<View
{...{
...props,
testID, // 👈
}}>
{children}
</View>
);
}
}
...
});
让我们在 UserScreen.test.js 文件中添加最后一个断言。
import React from "react"
import { render, waitFor } from "@testing-library/react-native"
import UserScreen from "../UserScreen"
import { check } from "react-native-permissions"
import Geolocation from "react-native-geolocation-service"
describe("<UserScreen />", () => {
test("should renders MapView and Marker with user current location", async () => {
const { getByTestId } = render(<UserScreen />) // 👈
await waitFor(() => {
expect(check).toHaveBeenCalledTimes(1)
expect(Geolocation.getCurrentPosition).toHaveBeenCalledTimes(1)
expect(getByTestId("map")).toBeDefined() // 👈
})
})
})
我们正在使用 React Testing Library Native getByTestId函数来断言 testID 已定义。
🛑 停!
我暂时完成了。请继续关注本教程的后续部分。也欢迎您留下您的评论。
您可以通过cristian.echeverri4@gmail.com邮箱联系我。也可以通过Twitter联系我。
鏂囩珷鏉ユ簮锛�https://dev.to/cecheverri4/google-maps-geolocation-and-unit-test-on-react-native-4eim