反应本机打字稿屏幕测试返回测试套件无法运行 AsyncStorage 为空

Posted

技术标签:

【中文标题】反应本机打字稿屏幕测试返回测试套件无法运行 AsyncStorage 为空【英文标题】:react native typescript screen test returning Test suite failed to run AsyncStorage is null 【发布时间】:2022-01-11 16:17:15 【问题描述】:

我在使用以下 URL 中的说明为 react-native typescript 屏幕编写测试时遇到困难 https://react-native-async-storage.github.io/async-storage/docs/advanced/jest/

我不断收到以下错误

失败 app/screens/login/login-screen.test.tsx ● 测试套件无法运行

[@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)

查看下面的登录屏幕测试文件内容

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")

)

测试\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__

和测试\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)

我的 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
    
  

实际的登录屏幕文件

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

我已经与这个问题斗争了将近一个星期 请建议我使用正确的语法来编写正确的测试,或者至少告诉我如何解决异步存储问题

【问题讨论】:

【参考方案1】:

根据您的 package.json 依赖项,您没有 @react-native-async-storage 依赖项,而是拥有 @react-native-community/async-storage,这是旧的组织命名空间。

尝试做

npm uninstall @react-native-community/async-storage
npm install @react-native-async-storage/async-storage

【讨论】:

谢谢@kieran,。它部分工作。现在我遇到了一个新错误。 react-native-permissions:NativeModule.RNPermissions 为空。要解决此问题,请尝试以下步骤: • 如果您在 iOS 上使用 CocoaPods,请在 ios 目录中运行 pod install,然后清理、重建并重新运行应用程序。您可能还需要重新打开 Xcode 以获取新的 pod。 • 如果您在单元测试时遇到此错误,您需要模拟本机模块。您可以使用它开始:github.com/react-native-community/react-native-permissions/blob/… @MIikeEps 该错误与此问题是分开的,并且很难通过 cmets 进行排序,但是请尝试输出中列出的步骤,如果您无法获得它,也许是一个新问题在那之后工作:)我建议如果你遇到问题尝试使用 react-native-clean-project 擦除所有构建工件并重新开始(所以运行 react-native-clean-project 然后执行 npm install 和 pod尝试在模拟器上再次运行之前安装在 ios 目录中) 感谢您抽出宝贵时间@Kieran,已提出一个新问题帖子。查看 URL >> ***.com/questions/70251955/… 我在错误建议中做了所有操作,但没有任何效果

以上是关于反应本机打字稿屏幕测试返回测试套件无法运行 AsyncStorage 为空的主要内容,如果未能解决你的问题,请参考以下文章

测试套件无法使用 @react-navigation/stack 运行

反应打字稿测试TypeError:MutationObserver不是构造函数

如何使用打字稿为反应测试库设置智能感知

反应本机打字稿如何键入 FlatList

使用打字稿配置本机反应时出错

useState 与接口 - 反应本机打字稿