I am experiencing difficulty writing tests for react-native that involve mocking Internet and Vibration permissions written in typescript
using instructions from the URL below
How to mock PermissionAndroid from react native
I keep getting the below error
FAIL app/screens/login/login-screen.test.tsx● Test suite failed to run react-native-permissions: NativeModule.RNPermissions is null. To fix this issue try these steps: • If you are using CocoaPods on iOS, run `pod install` in the `ios` directory and then clean, rebuild and re-run the app. You may also need to re-open Xcode to get the new pods.• If you are getting this error while unit testing you need to mock the native module. You can use this to get started: https://github.com/react-native-community/react-native-permissions/blob/master/mock.js If none of these fix the issue, please open an issue on the Github repository: https://github.com/react-native-community/react-native-permissions at Object.<anonymous> (node_modules/react-native-qrcode-scanner/node_modules/react-native-permissions/lib/commonjs/index.ts:6:9) at Object.<anonymous> (node_modules/react-native-qrcode-scanner/index.js:19:1)
See the login screen test file content below
import AsyncStorage from "@react-native-community/async-storage"import { render } from "@testing-library/react-native"import React from "react"import { LoginScreen } from ".."it("renders correctly", async () => { const { getAllByText } = await render(<LoginScreen navigation={undefined} route={undefined} />) expect(getAllByText("Login").length).toBe(1) expect(AsyncStorage.getItem).toBeCalledWith("myKey")})
test\setup.ts
// we always make sure 'react-native' gets included firstimport "react-native"// libraries to mockimport "./mock-react-native-image"import "./mock-async-storage"import "./mock-i18n"import "./mock-reactotron"import "./mock-permissions"jest.useFakeTimers()declare global { let __TEST__}
and test\mock-permissions.ts
// eslint-disable-next-line react-native/split-platform-componentsimport { PermissionsAndroid } from "react-native";const internetPermissionResult: string = PermissionsAndroid.RESULTS.GRANTED;const vibratePermissionResult: string = PermissionsAndroid.RESULTS.GRANTED;const internetPermissionGranted = true;const vibratePermissionGranted = true;const permissionsAndroidModule = jest.requireActual('react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js');jest.doMock('react-native/Libraries/PermissionsAndroid/PermissionsAndroid', () => ({ ...permissionsAndroidModule, requestMultiple: () => { return { [PermissionsAndroid.PERMISSIONS.INTERNET]: internetPermissionResult, [PermissionsAndroid.PERMISSIONS.VIBRATE]: vibratePermissionResult, }; }, check: () => { return internetPermissionGranted && vibratePermissionGranted; },}));
my package.json
{"name": "special-project","version": "0.0.1","private": true,"scripts": {"start": "react-native start","ios": "react-native run-ios","android": "react-native run-android","test:e2e": "detox test -c ios.sim.debug","build:e2e": "detox build -c ios.sim.debug","ci:test:e2e": "detox test -c ios.sim.release -l verbose --cleanup","ci:build:e2e": "detox build -c ios.sim.release","compile": "tsc --noEmit -p . --pretty","format": "npm-run-all format:*","format:js": "prettier --write \"app/**/*.js\"","format:json": "prettier --write \"app/**/*.json\"","format:md": "prettier --write \"**/*.md\"","format:ts": "prettier --write \"app/**/*.ts{,x}\"","lint": "eslint index.js app storybook test --fix --ext .js,.ts,.tsx && yarn format","patch": "patch-package","storybook": "start-storybook -p 9001 -c ./storybook","test": "jest","adb": "adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081","postinstall": "node ./bin/postInstall","build-ios": "react-native bundle --entry-file index.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios","build-android": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res","clean": "react-native-clean-project","clean-all": "npx react-native clean-project-auto" },"dependencies": {"@react-native-async-storage/async-storage": "^1.15.14","@react-native-community/checkbox": "^0.5.9","@react-native-community/masked-view": "0.1.10","@react-navigation/drawer": "^6.1.8","@react-navigation/material-top-tabs": "^6.0.6","@react-navigation/native": "~6.0.1","@react-navigation/native-stack": "^6.0.2","@react-navigation/stack": "~6.0.1","@reduxjs/toolkit": "^1.6.2","@unimodules/core": "6.0.0","apisauce": "2.0.0","axios": "^0.24.0","expo-linear-gradient": "^9.2.0","expo-localization": "9.1.0","i18n-js": "3.8.0","mobx": "6.1.8","mobx-react-lite": "3.2.0","mobx-state-tree": "5.0.1","node-fetch": "^3.1.0","react": "17.0.1","react-native": "0.64.2","react-native-appearance": "^0.3.4","react-native-camera": "^4.2.1","react-native-gesture-handler": "^1.10.3","react-native-image-crop-picker": "^0.36.4","react-native-keychain": "6.2.0","react-native-material-menu": "^2.0.0","react-native-pager-view": "^5.4.8","react-native-paper": "^4.10.1","react-native-permissions": "^3.1.0","react-native-qrcode-scanner": "^1.5.4","react-native-radio-buttons-group": "^2.2.7","react-native-reanimated": "^2.2.3","react-native-safe-area-context": "3.1.8","react-native-screens": "3.4.0","react-native-tab-view": "^3.1.1","react-native-unimodules": "0.12.0","react-redux": "^7.2.6","reactotron-mst": "3.1.3","reactotron-react-js": "^3.3.7","redux": "^4.1.2","redux-logger": "^3.0.6","redux-persist": "^6.0.0","redux-saga": "^1.1.3","validate.js": "0.13.1" },"devDependencies": {"@babel/core": "^7.12.9","@babel/plugin-proposal-decorators": "7.12.1","@babel/plugin-proposal-optional-catch-binding": "7.12.1","@babel/runtime": "^7.12.5","@storybook/addon-storyshots": "6.1.10","@storybook/react-native": "5.3.23","@storybook/react-native-server": "5.3.23","@testing-library/react-native": "^8.0.0","@types/i18n-js": "3.0.3","@types/jest": "26.0.19","@types/react": "16.14.0","@types/react-native": "0.63.40","@types/react-test-renderer": "16.9.4","@typescript-eslint/eslint-plugin": "4.10.0","@typescript-eslint/parser": "4.10.0","babel-jest": "26.6.3","babel-loader": "8.2.2","detox": "17.14.5","eslint": "7.15.0","eslint-config-prettier": "7.0.0","eslint-config-standard": "16.0.2","eslint-plugin-import": "2.22.1","eslint-plugin-node": "11.1.0","eslint-plugin-promise": "4.2.1","eslint-plugin-react": "7.21.5","eslint-plugin-react-native": "3.10.0","fbjs-scripts": "3.0.0","jest": "^25.5.4","jest-circus": "25.5.4","jest-expo": "^40.0.1","jetifier": "1.6.6","npm-run-all": "4.1.5","patch-package": "6.2.2","postinstall-prepare": "1.0.1","prettier": "2.2.1","react-devtools-core": "4.10.1","react-dom": "^17.0.2","react-native-clean-project": "^3.6.3","react-native-web": "^0.16.3","react-powerplug": "1.0.0","reactotron-react-native": "^5.0.0","solidarity": "2.3.1","typescript": "4.2.3" },"jest": {"preset": "jest-expo","setupFiles": ["<rootDir>/test/setup.ts" ],"testPathIgnorePatterns": ["/node_modules/","/e2e" ],"transformIgnorePatterns": ["node_modules/(?!(jest-)?react-native|expo-linear-gradient|@react-native|@react-native-async-storage|@react-navigation|@storybook|@react-native-community|expo-localization|@unimodules)" ] },"prettier": {"printWidth": 100,"semi": false,"singleQuote": false,"trailingComma": "all" },"detox": {"test-runner": "jest","configurations": {"ios.sim.debug": {"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/codix.app","build": "xcodebuild -workspace ios/codix.xcworkspace -scheme codix -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build -destination 'name=iPhone 11'","type": "ios.simulator","device": {"name": "iPhone 11","os": "iOS 13.2" } },"ios.sim.release": {"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/codix.app","build": "xcodebuild -workspace ios/codix.xcworkspace -scheme codix -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -destination 'name=iPhone 11'","type": "ios.simulator","device": {"name": "iPhone 11","os": "iOS 13.2" } } } },"eslintConfig": {"root": true,"parser": "@typescript-eslint/parser","extends": ["plugin:@typescript-eslint/recommended","plugin:react/recommended","plugin:react-native/all","standard","prettier","prettier/@typescript-eslint" ],"plugins": ["@typescript-eslint","react","react-native" ],"parserOptions": {"ecmaFeatures": {"jsx": true },"project": "./tsconfig.json" },"settings": {"react": {"pragma": "React","version": "detect" } },"globals": {"__DEV__": false,"jasmine": false,"beforeAll": false,"afterAll": false,"beforeEach": false,"afterEach": false,"test": false,"expect": false,"describe": false,"jest": false,"it": false },"rules": {"@typescript-eslint/ban-ts-ignore": 0,"@typescript-eslint/explicit-function-return-type": 0,"@typescript-eslint/explicit-member-accessibility": 0,"@typescript-eslint/explicit-module-boundary-types": 0,"@typescript-eslint/indent": 0,"@typescript-eslint/member-delimiter-style": 0,"@typescript-eslint/no-empty-interface": 0,"@typescript-eslint/no-explicit-any": 0,"@typescript-eslint/no-object-literal-type-assertion": 0,"@typescript-eslint/no-var-requires": 0,"comma-dangle": 0,"multiline-ternary": 0,"no-undef": 0,"no-unused-vars": 0,"no-use-before-define": "off","quotes": 0,"react-native/no-raw-text": 0,"react/no-unescaped-entities": 0,"react/prop-types": "off","space-before-function-paren": 0 } }}
The actual login screen file
import React, { FC, useState } from "react"import { View, SafeAreaView, TouchableOpacity, ScrollView, TextInput, ImageBackground, KeyboardAvoidingView, Alert,} from "react-native"import { StackScreenProps } from "@react-navigation/stack"import { observer } from "mobx-react-lite"import { Button, Text, AutoImage as Image } from "../../components"import { NavigatorParamList } from "../../navigators"import { Images } from "../../config"import { setIsLoggedIn } from "../../reducers/loginReducer"import { useDispatch } from "react-redux"import { setTokenValue } from "../../reducers/tokenReducer"import { LOGIN_URL, showErrorAlert } from "../../utils/constants"import axios from "axios"import { setSalesAgentIdValue } from "../../reducers/salesAgentIdReducer"import { BLUESIGNUP_TEXT, BOTTOM_HALF, CONTINUE, CONTINUE_TEXT, FOOTER_CONTENT, FULL, HOME_LOGO, KEYBOARD_AVOID_VIEW, LABEL, LOGO_TEXT, RED_ACTION_LINK, RED_TEXT, REGULAR_TEXT, SUBJECT, TEXT_INPUT, TEXT_INPUT_END,} from "./loginscreen-styles"import { setRefreshTokenValue } from "../../reducers/refreshTokenReducer"export const LoginScreen: FC<StackScreenProps<NavigatorParamList, "login">> = observer( ({ navigation }) => { const dispatch = useDispatch() const [email, setEmail] = useState("") const [password, setPassword] = useState("") const [errortext] = useState("") let tokenValue: any let refreshTokenValue: any let salesAgenIdValue: any const api = axios.create({ baseURL: LOGIN_URL, }) const onLogin = async () => { if (!email) { showErrorAlert("Your Email") return } if (!password) { showErrorAlert("Your Password") return } console.log("<<< inside onlogin >>>>>") const dataToSend = { email: email, password: password, } try { console.log("<<<<<<< BEFORE RESPONSE FOR LOGIN >>>>>>>>>") const res = await api.post("/", dataToSend) console.log("Res >> ", res) console.log("<<<<<<<AFTER RESPONSE FOR LOGIN >>>>>>>>>") // eslint-disable-next-line no-prototype-builtins if (res.hasOwnProperty("data")) { tokenValue = res.data.data.userToken refreshTokenValue = res.data.data.refreshToken console.log("<<<<<<<TOKEN>>>>>>>>>") console.log(JSON.stringify(tokenValue)) console.log("<<<<<<<TOKEN>>>>>>>>>") console.log("<<<<<<< REFRESH TOKEN>>>>>>>>>") console.log(JSON.stringify(refreshTokenValue)) console.log("<<<<<<< REFRESH TOKEN>>>>>>>>>") dispatch(setTokenValue(tokenValue)) dispatch(setRefreshTokenValue(refreshTokenValue)) dispatch(setIsLoggedIn(true)) salesAgenIdValue = res.data.data.userId console.log("<<<<<<<salesAgenIdValue>>>>>>>>>") console.log(JSON.stringify(salesAgenIdValue)) console.log("<<<<<<<salesAgenIdValue>>>>>>>>>") dispatch(setSalesAgentIdValue(salesAgenIdValue)) setTimeout(() => { navigation.navigate("drawer") }, 1000) } else { console.log("<<<<<<< ERRRRRROR >>>>>>>>>") } } catch (err) { console.log(err) Alert.alert("An Error occurred " + err) } } return (<ImageBackground source={Images.bg} style={FULL}><Image source={Images.wragbyLogo} style={HOME_LOGO} /><View style={LOGO_TEXT}><Text>Special App</Text></View><View style={BOTTOM_HALF}><SafeAreaView><View><Text style={RED_TEXT}>{errortext}</Text></View><ScrollView showsVerticalScrollIndicator={false}><KeyboardAvoidingView style={KEYBOARD_AVOID_VIEW}><Text style={SUBJECT}>Login</Text><Text style={LABEL}>Email</Text><TextInput style={TEXT_INPUT} placeholderTextColor="#707070" onChangeText={(email) => setEmail(email)} placeholder="" autoCapitalize="none" value={email} /><Text style={LABEL}>Password</Text><TextInput style={TEXT_INPUT_END} placeholderTextColor="#707070" onChangeText={(password) => setPassword(password)} secureTextEntry={true} placeholder="" autoCapitalize="none" value={password} /><View style={RED_ACTION_LINK}><TouchableOpacity onPress={() => navigation.navigate("resetpassword")}><Text style={RED_TEXT}>Forgot password?</Text></TouchableOpacity></View><View style={FOOTER_CONTENT}><Button testID="next-screen-button" style={CONTINUE} textStyle={CONTINUE_TEXT} tx="welcomeScreen.signIn" // onPress={showDashboard} onPress={onLogin} /><TouchableOpacity onPress={() => navigation.navigate("signup")}><Text style={REGULAR_TEXT}> Don't have an Account yet? <Text style={BLUESIGNUP_TEXT}>Sign up</Text></Text></TouchableOpacity></View></KeyboardAvoidingView></ScrollView></SafeAreaView></View></ImageBackground> ) },)
I have been battling with this issue for almost a weekPlease advise me with the right syntax to use in writing proper tests or at least tell me how to resolve the async storage problem