I am experiencing difficulty writing tests for react-native typescript screen using instructions from the URL below https://react-native-async-storage.github.io/async-storage/docs/advanced/jest/
I keep getting the below error
FAIL app/screens/login/login-screen.test.tsx ● Test suite failed to run
[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
• Run `react-native link @react-native-community/async-storage` in the project root.
• Rebuild and restart the app.
• Run the packager with `--reset-cache` flag.
• If you are using CocoaPods on iOS, run `pod install` in the `ios` directory and then rebuild and re-run the app.
• If this happens while testing with Jest, check out docs how to integrate AsyncStorage with it: https://react-native-community.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the Github repository: https://github.com/react-native-community/react-native-async-storage/issues
at Object.<anonymous> (node_modules/@react-native-community/async-storage/lib/commonjs/AsyncStorage.native.js:17:9)
at Object.<anonymous> (node_modules/@react-native-community/async-storage/lib/commonjs/index.js:6: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 first
import "react-native"
// libraries to mock
import "./mock-react-native-image"
import "./mock-async-storage"
import "./mock-i18n"
import "./mock-reactotron"
jest.useFakeTimers()
declare global {
let __TEST__
}
and test\mock-async-storage.ts
import mockAsyncStorage from "@react-native-async-storage/async-storage/jest/async-storage-mock"
jest.mock("@react-native-async-storage/async-storage", () => mockAsyncStorage)
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-community/async-storage": "^1.12.1",
"@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/special-project.app",
"build": "xcodebuild -workspace ios/special-project.xcworkspace -scheme special-project -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/special-project.app",
"build": "xcodebuild -workspace ios/special-project.xcworkspace -scheme special-project -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 week Please advise me with the right syntax to use in writing proper tests or at least tell me how to resolve the async storage problem
Based on your package.json dependencies you dont have the @react-native-async-storage
dependency, you have @react-native-community/async-storage
, which is the old organisation namespace.
Try doing
npm uninstall @react-native-community/async-storage
npm install @react-native-async-storage/async-storage