Google Maps, Geolocation and Unit Test on React Native
Tags: reactnative, testing, googlecloud, react
Published: 2021-07-26 12:20:20

In this tutorial, we will build a Taxi App for both iOS & Android using React Native CLI. Also, we're going to dive into Unit Testing our entire app using Jest + React Testing Library.

[Source Code](https://github.com/crisecheverria/taxi-app-tutorial/tree/part-1)

I also have a Video Version, which is a Pro feature of this tutorial. Check it out 🚨🚀👉 [Video Version](https://crisecheverria.gumroad.com/l/TEEet)

## Environment setup

First, make sure you have the development environment ready before you start. I'm using a macOS Laptop with two iPhones: iPhone 12 & iPhone SE 2020. Although it isn't necessary to have an actual device to test the App, you can use an Emulator if you don't have an Android/iPhone, but it's recommended that you test the App on a real device.

I'm using React Native version 0.64, be sure you have the same version to avoid compatibility issues using the main libraries like react-native-maps and others. I will try to keep the tutorial up to date with the major releases on React Native CLI.

Follow this [link](https://reactnative.dev/docs/environment-setup) to install the local environment. Let's go!!

## Create the App

Let's create the app using npx command

```js
npx react-native init taxiApp --version 0.64.2
```

Create an **src** folder and move the App.js file into that location. In the end, you should have **./src/App.js**. We aren’t using TypeScript (TS) for this project, so remove everything related to TS and convert TypeScript App function into a regular JavaScript function.

```js
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
```

I have placed a temporary _Welcome to Taxi App_ message with some styles using **StyleSheet** object from React Native, which is nice, but this will be temporary because we will move into **Styled Components** soon.

Because I moved _App.js_ into the _src_ folder, we have to update _App.js_ to import the new location inside the main **index.js**.

From

```js
import App from "./App"
```

To

```js
import App from "./src/App"
```
## Run in the Emulator

First we will need to start Metro.
To start Metro, run **npx react-native start** inside your React Native project folder:

```js
npx react-native start
```

Let Metro Bundler run in its own terminal. Open a new terminal inside your React Native project folder. Run the following:

```js
npx react-native run-ios
```

If everything is set up correctly, you should see your new app running in your iPhone emulator shortly.

For Android, first run the Android Emulator, then run this command:

```js
npx react-native run-android
```

## Install React Native Maps

The default library to use for working with Maps in React Native is [react-native-maps](https://github.com/react-native-maps/react-native-maps), and we're going to use that next.

```js
npm install react-native-maps --save-exact
```

We will use Google Maps instead of Apple Maps because Apple Maps only works out of the box for iOS devices. To continue, we will need to have an **API key for Android SDK** & the same for **iOS SDK**. You need to create a [Google billing acount](https://developers.google.com/maps/gmp-get-started#create-billing-account), so click the link and create an account.

### Google Billing Account

Once you created an account with Google Cloud, you will need to create a New Project. Inside the New Project, we will create **API & Services** for accessing **Maps SDK for Android**, **Maps SDK for iOS**, and more.

1. Create a new Project inside [console.cloud.google.com](console.cloud.google.com)
2. Click API & Services > Credentials.
3. Click on + CREATE CREDENTIALS. It will immediately give you an API key. That’s the one we’re going to use to set Google Maps on both iOS and Android.
4. Click on the recently created credentials to restrict the key. Search for the API restrictions section. You will see an option to Restrict key. There make sure you select **Maps SDK for Android** and **Maps SDK for iOS**.
5. Click Save

![Google Billing Account](https://cristianecheverria.com/static/cd406040707a895ee8a353a5dc868d80/2d849/maps-api-key.png)

### Build configuration on iOS

#### Set the usage description property

The app's **Info.plist** file must contain a **NSLocationWhenInUseUsageDescription** with a user-facing purpose string explaining clearly and completely why your app needs the location, otherwise Apple will reject your app submission.

Inside your **./ios/taxiApp/Info.plist**, make sure you have this:

```js
...
<key>NSLocationWhenInUseUsageDescription</key>
<string>In order to work we need you to grant location access</string>
...
```

#### Enabling Google Maps in iOS

If you want to enable Google Maps on iOS, copy the Google API key and edit your **./ios/taxiApp/AppDelegate.m** as follows:

```js
+ #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
...
```

The [GMSServices provideAPIKey] should be the first call of the method.

Add the following to your **Podfile** above the **config = use_native_modules!**:

```js
# 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'
```

Now, we're going to build for iOS using CocoaPods. After installing the npm package, we need to install the pod.

```js
npx pod-install
```

### Build configuration on Android

Configure Google Play Services. Inside **android/build.gradle** add this two lines:

```js
ext {
        buildToolsVersion = "29.0.3"
        minSdkVersion = 21
        compileSdkVersion = 29
        targetSdkVersion = 29
        ndkVersion = "20.1.5948944"
        playServicesVersion = "17.0.0"    // <= 👈
        androidMapsUtilsVersion = "2.2.0" // <= 👈
    }
```

Specify your Google Maps API key. Add your API key to your manifest file (android/app/src/main/AndroidManifest.xml):

```js
<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>
```

## Start using React Native Maps

Open the App.js file and replace the content with the following code. The purpose is to show Google Map covering the device's entire screen. We're adding new styles for that:

```js
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
```

If you're running the App on the Emulator, you should see something like this:

![Google Maps Cover](https://cristianecheverria.com/static/e6916f9653a867dcdc6d60f15d533e27/c07e5/first-map-cover.png)

## Show user location

We will ask for location permission from the user to show the user's location on the map. For that, we're going to use the package **react-native-permissions**. You can follow the instructions on how to set up for iOS & Android [here](https://github.com/zoontek/react-native-permissions#setup).

```js
npm install --save-exact react-native-permissions@3.0.1
```

For iOS setup you have to open the **Podfile** inside ios folder and add next lines of code:

```js
# React Native Permissions
permissions_path = '../node_modules/react-native-permissions/ios'
pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse"
```

Previously we already added inside Info.plist next lines, but you can double-check:

```js
<key>NSLocationWhenInUseUsageDescription</key>
<string>In order to work we need you to grant location access</string>
```

Now using Cocoa Pods install the dependencies for finishing the process for ios.

```js
npx pod-install
```

For Android you only have to update the **android/app/src/main/AndroidManifest.xml** file.

```js
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
```

Now, lets use react native permissions to show user location. Inside your App.js file add next lines:

```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
```

If you run the App using the iOS Emulator you will see a Popup windows asking for permission, that you have to approve.

![Grant User Location Permission](https://cristianecheverria.com/static/d7dbc174b1d4a75f21c6d78f195388a6/c07e5/grant-location-permission.png)

Once you approve, you should see a blue circle indicating your current location. If isn't that the case, perhaps it's because your Emulator isn't using a Custom Location, and you have to update that. For that, go into the Emulator menu and follow this:

1. Click on Features > Location > Custom Location
2. Type this location: Latitude: 57,705871 & Longitude: 11,938823

This location is based on the map initialRegion, which in the code above its:

```js
initialRegion={{
    latitude: 57.709127,
    longitude: 11.934746,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
}}
```

You can see that my Custom Location is inside my initial map region, which is the city of Gothenburg, Sweden.

## Track user Location

Previously, we only show the user's current location, but we need to track the user location continuously when the user change position. For that, we're going to use **react-native-geolocation-service**.

```js
npm install react-native-geolocation-service@5.2.0 --save-exact
```

### Setup

You can follow the more detail guide on their [documentation](https://github.com/Agontuk/react-native-geolocation-service/blob/master/docs/setup.md). We already setup Location Permission for both iOS & Android. I the case for iOS we will run:

```js
npx pod-install
```

Let’s update our App.js component to track the user location using Geolocation.

```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
```

To resume.

1. Added local state using useState `const [location, setLocation] = useState(null)`.
2. Using useEffect when component mount we call **Geolocation** service and update the **location** state.
3. Added a condition to show the `<MapView />` component only if **location** state isn't null.

## Add Custom Style & Props to MapView

We can change the colors and general look of the Google Map. Also, the MapView component comes with several valuable props we are going to add a few of them, but you can find a complete list [here](https://github.com/react-native-maps/react-native-maps/blob/master/docs/mapview.md).

Let's create a new folder inside the src folder named styles; this will be our general styles location that we will add more
(**./src/styles/index.js**)

```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",
      },
    ],
  },
]
```

Now, let's create more folders inside src. Next will be the screens folder where we’re going to make our first screen named UserScreen.js. Inside UserScreen, we will move the content of our App.js. (**./src/screens/UserScreen.js**)

```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
```

After that, our App.js component no longer will have the above code. Instead, we’re going to import the `<UserScreen />` component and render that. We will use React Navigation inside App.js to handle all the screens of our app in the future.

```js
/**
  For now just import and render <UserScreen />
 */

import React from "react"
import UserScreen from "./screens/UserScreen"

const App = () => {
  return <UserScreen />
}

export default App
```

This is how should look our folders directory.

![Folders organization](https://cristianecheverria.com/static/9b2ead67e969723e75f822c586ea394d/d19c0/folder-directory.png)

Finally, let's use our customMapStyle and other props into MapView on UserScreen.js

```js
...
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;
```

Our Map new look! 😍

![Map Custom Style](https://cristianecheverria.com/static/b719c225b9e25c24ea56cc7b41bb5288/2d849/emulators.png)

## Unit Test

It's Tests time! 😍

We are going to dive into our first test case for the `<UserScreen />` component and we're goin to use:

1. Jest
2. [React Testing Library Native](https://callstack.github.io/react-native-testing-library/) 📚

So, lets install React Testing Library Native

```js
npm install --save-dev @testing-library/react-native
```

Also, lets install additional jest matchers:

```js
npm install --save-dev @testing-library/jest-native
```

Then automatically add it to your jest tests by using **setupFilesAfterEnv** option in your Jest configuration (it's usually located either in package.json under "jest" key or in a jest.config.json file):

```js
// package.json
"jest": {
    "preset": "react-native",
    // 👇
    "setupFilesAfterEnv": [
      "@testing-library/jest-native/extend-expect"
    ]
  }
```

Now, we’re using a couple of libraries like **Geolocation**, **react-native-permissions** and **MapView** components we need to create mocks of those components/libraries. For that, let’s make a file named **jest-setup.js** in the root of your app. For now, we will only import jest.

```js
// ./jest-setup.js
import { jest } from "@jest/globals"
```

Alright, alright, let's create our first test for the `<UserScreen />` component. For that, inside **src/screens**, create `__tests__` (double underscore, both sides) folder. Inside create the file named **UserScreen.test.js**.

```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 />)
  })
})
```

Now, what happens if we try to run the **test** command that we already have in the package.json file?

```js
npm run test
```

After running the test command, you will notice that **Jest** tried to run the two tests files we have. That command will run All the test files we defined in our app. We already have one test by default, and that comes from App.js file. The second test is the one that we write above.

Also, you will see that the test failed! 😱

![Failed Test](https://cristianecheverria.com/static/d26f06165d131f09e5a7598949b819b6/2d849/test-failed.png)

The test failed, and it's completely normal. You can see that the issue was that Jest tried to import **MapView** component from **react-native-maps** library for the UserScreen.test.js file and it failed. That's why we need to mock react-native-maps so our test can pass.

Let's do that! 💪

Open the **jest-setup.js** file and mock react-native-maps.

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

  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",
  }
})
```

We created a mock for react-native-maps. We used a React Class component, mostly because I had an issue when used a Function Component. Perhaps you could try that instead of a class. We grab whatever props our MapView component could have and also whatever content we pass as children. Lastly, we return MockMapView as default export because when we import MapView from react-native-maps you can see it is a default export.

Next, we need to tell Jest that we have a **setupFiles** for our tests. We do that inside our jest section on package.json.

```js
"jest": {
    "preset": "react-native",
    "setupFilesAfterEnv": [
      "@testing-library/jest-native/extend-expect"
    ],
    // 👇
    "setupFiles": [
      "./jest-setup.js"
    ]
  }
```

Try to run the test command again

```js
npm run test
```

And... It fail!

![Second Failed Test](https://cristianecheverria.com/static/2a6ce576ba6eb4f3401f1a7dc5f21b0f/2d849/second-test-failed.png)

This time atleast isn't failing on MapView. This time is failing because of **react-native-permissions**. And it's because we haven't mock that one yet.

Let's do that! 💪

Go back to **jest-setup.js** and add this:

```js
jest.mock("react-native-permissions", () =>
  require("react-native-permissions/mock")
)
```

If for some reasons you still have issues regarding `export {PERMISSIONS, RESULT}` then you could try to fix adding **transformIgnorePatterns** to your Jest config inside package.json

```js
 "transformIgnorePatterns": [
      "node_modules/(?!(jest-)?react-native-permissions|)" // See I added react-native-permissions
    ],
```

There is one last mock that we’re missing, and that is Geolocation. Instead of mocking inside our jest-setup.js file, let's create a `__mocks__` folder at the root of our project. Inside the mocks folder, add the name of the library **react-native-geolocation-service.js**. It has to be the same name.

```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)
  }),
}
```

Phew, I think we finished mocking our libraries/components from external packages. It is time to rerun the test, but we can remove the `__tests__/App.test.js` file. We don’t go to test App.js for now. We‘re focusing on `src/screens/__tests__/UserScreen.test.js`.

```js
npm run test
```

And... it should PASS!!

![Test Pass](https://cristianecheverria.com/static/13505c79848c81308556fd81856ad795/2d849/tests-pass.png)

We only tested that the UserScreen component renders. Let's increase our code coverage by testing if the Map is rendered and called for location permissions.

```js
// 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)
    })
  })
})
```

We can safely import the **check** function from our mocked react-native-permissions library. Same for **Geolocation**. We use async/await and **waitFor** from React Testing Library Native because when component mount, we check for permissions first. Second, we call the current user location. Then we update the state with setLocation({latitude, longitude}). So, there're a couple of things going on, and we have to wait to fulfill those operations.

Let's add a testID to our MapView component to make sure that the Map is rendered. Open UserScreen component and add a testID.

```js
...

const UserScreen = () => {
  ...

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" />
      {location && (
        <MapView
          testID="map" // 👈
          ...
        />
      )}
    </SafeAreaView>
  );
};

...
```

We also need to pass the new testID into our mocked version the MapView component. So open jest-setup.js file and add this:

```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>
      );
    }
  }

...
});
```

Lets add one last assertion to our UserScreen.test.js file.

```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() // 👈
    })
  })
})
```

We 're using React Testing Library Native **getByTestId** function to assert that the testID its defined.

🛑 Stop!

I'm finishing for now. Stay tuned for the following parts of this tutorial. Also, feel free to leave your comments.

You can reach me by email at cristian.echeverri4@gmail.com. Also, on [Twitter](https://twitter.com/cecheverri4)