【问题标题】:Configure jest into exist react native app将 jest 配置为现有的 react native 应用程序
【发布时间】:2021-11-10 12:42:22
【问题描述】:

我有一个应用,想添加一些快照测试, 这是我第一次从事测试工作, 因此,在尝试运行 jest 之后,他们要求我使用 Mock 包或其他东西,所以我为他们添加了 mocking jest

然后运行test

我得到了错误。

我的配置好吗?还是我错过了什么?

 console.error
    Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

    Check the render method of `App`.
        in App
 console.error
    The above error occurred in the <Context.Provider> component:
        in EnsureSingleNavigator (created by ForwardRef(BaseNavigationContainer))
        in ForwardRef(BaseNavigationContainer) (created by ForwardRef(NavigationContainer))
        in ThemeProvider (created by ForwardRef(NavigationContainer))
        in ForwardRef(NavigationContainer) (created by App)
        in QueryClientProvider (created by App)
        in App

.....

这是我的配置和测试 应用程序.tsx

import React, {useEffect} from 'react';
import './src/i18n';
import {NavigationContainer} from '@react-navigation/native';
import AuthNavigator from './src/navigation/AuthNavigator';
import RootNavigator from './src/navigation/RootNavigator';
import {StatusBar} from 'react-native';
import {QueryClient, QueryClientProvider} from 'react-query';
import {useAuth} from './src/stores/authStore';
import messaging from '@react-native-firebase/messaging';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as Sentry from '@sentry/react-native';

Sentry.init({
  dsn: 'https://********@*******.ingest.sentry.io/****',
});

const queryClient = new QueryClient();

export default function App() {
  const isLogin = useAuth(state => state.isLogin);

  async function requestUserPermission() {
    let fcmToken = await AsyncStorage.getItem('device_token');
    if (!fcmToken) {
      await messaging().requestPermission();
    }
  }

  useEffect(() => {
    requestUserPermission();
  }, []);

  useEffect(() => {
    const unsubscribe = messaging().onMessage(async remoteMessage => {
      remoteMessage?.data?.type === 'waiting'
        ? queryClient.invalidateQueries('getBookingsList')
        : null; // refetch Booking list when receive notification from server
    });

    return unsubscribe;
  }, []);

  return (
    <QueryClientProvider client={queryClient}>
      <NavigationContainer>
        <StatusBar barStyle="light-content" />
        {isLogin ? <RootNavigator /> : <AuthNavigator />}
      </NavigationContainer>
    </QueryClientProvider>
  );
}

_ 测试 _/App-test.tsx

import 'react-native';
import React from 'react';
import App from '../App';
import {waitFor} from '@testing-library/react-native';
import renderer from 'react-test-renderer';

describe('App', () => {
  it('renders app stack correctly', async () => {
    const component = <App />;
    await waitFor(() => renderer.create(component));
  });
});

jest.setup.js

import mockRNDeviceInfo from 'react-native-device-info/jest/react-native-device-info-mock';
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';

jest.mock('@sentry/react-native', () => ({init: () => jest.fn()}));

jest.mock('react-native-device-info', () => mockRNDeviceInfo);
require('react-native-reanimated/lib/reanimated2/jestUtils').setUpTests();
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);

jest.mock('@react-native-firebase/messaging', () => {
  const module = () => {
    return {
      getToken: jest.fn(() => '1234'),
    };
  };
  module.AuthorizationStatus = {
    NOT_DETERMINED: -1,
    DENIED: 0,
    AUTHORIZED: 1,
    PROVISIONAL: 2,
  };

  return module;
});

package.json

{
  "name": "myapp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.15.5",
    "@react-native-community/masked-view": "^0.1.11",
    "@react-native-firebase/app": "^12.7.5",
    "@react-native-firebase/messaging": "^12.7.5",
    "@react-navigation/native": "^5.9.4",
    "@react-navigation/stack": "^5.14.5",
    "@sentry/react-native": "^3.0.1",
    "axios": "^0.21.1",
    "dayjs": "^1.10.6",
    "formik": "^2.2.9",
    "i18next": "^20.3.2",
    "react": "17.0.1",
    "react-i18next": "^11.11.1",
    "react-native": "0.64.2",
    "react-native-country-picker-modal": "^2.0.0",
    "react-native-device-info": "^7.3.1",
    "react-native-gesture-handler": "^1.10.3",
    "react-native-keyboard-aware-scroll-view": "^0.9.4",
    "react-native-reanimated": "^2.2.0",
    "react-native-safe-area-context": "^3.2.0",
    "react-native-screens": "^3.4.0",
    "react-native-size-matters": "^0.4.0",
    "react-native-svg": "^12.1.1",
    "react-query": "^3.19.2",
    "yup": "^0.32.9",
    "zustand": "^3.5.7"
  },
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/preset-env": "^7.15.6",
    "@babel/preset-typescript": "^7.15.0",
    "@babel/runtime": "^7.8.4",
    "@react-native-community/eslint-config": "^3.0.0",
    "@testing-library/jest-native": "^4.0.2",
    "@testing-library/react-native": "^7.2.0",
    "@types/jest": "^25.2.3",
    "@types/react-native": "^0.63.2",
    "@types/react-test-renderer": "^16.9.2",
    "babel-jest": "^25.1.0",
    "eslint": "^7.32.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "jest": "^25.1.0",
    "metro-react-native-babel-preset": "^0.59.0",
    "prettier": "^2.3.2",
    "react-test-renderer": "16.13.1",
    "ts-jest": "^27.0.0-next.12",
    "ts-node": "^10.2.1",
    "typescript": "^3.8.3"
  },
  "resolutions": {
    "@types/react": "^16"
  },
  "jest": {
    "verbose": true,
    "preset": "react-native",
    "setupFiles": [
      "<rootDir>/jest.setup.js",
      "./node_modules/react-native-gesture-handler/jestSetup.js"
    ],
    "testPathIgnorePatterns": [
      "./node_modules/"
    ],
    "transformIgnorePatterns": [
      "node_modules/(?!(jest-)?react-native|@react-native-community|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|@react-native|react-native-gesture-handler|react-native-device-info|/.*)"
    ],
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  }
}

babel.config.js

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin'],
};

tsconfig.json

{
  "compilerOptions": {
    /* Basic Options */
    "target": "esnext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
    "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "lib": [
      "es2017"
    ] /* Specify library files to be included in the compilation. */,
    "allowJs": true /* Allow javascript files to be compiled. */,
    // "checkJs": true,                       /* Report errors in .js files. */
    "jsx": "react-native" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "removeComments": true,                /* Do not emit comments to output. */
    "noEmit": true /* Do not emit outputs. */,
    // "incremental": true,                   /* Enable incremental compilation */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
    "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    "skipLibCheck": false /* Skip type checking of declaration files. */,

    /* Source Map Options */
    // "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
    "noUncheckedIndexedAccess": true
  },
  "exclude": [
    "node_modules",
    "babel.config.js",
    "metro.config.js",
    "jest.config.ts"
  ]
}

【问题讨论】:

    标签: javascript reactjs typescript react-native jestjs


    【解决方案1】:

    您似乎已经在使用@testing-library/react-native(这很好) - 所以我建议切换到使用他们的渲染方法而不是 react-native-renderer 例如:

    import { waitFor, render } from "@testing-library/react-native"
    
    describe('App', () => {
      it('renders app stack correctly', async () => { 
         render(<App />)
    // (If you want to check anything, you then can pick off the return of render like so:
    // const {queryAllByA11yHint} = render(<App />)
      });
    });
    

    关于实际问题 - 问题是渲染器需要一个组件,而不是一个元素 - 如果您更改为我在上面显示的内容,它将起作用,或者如果您仍想使用您的渲染器,它会像这样:

    
    describe('App', () => {
      it('renders app stack correctly', async () => {
        await waitFor(() => renderer.create(<App />));
      });
    });
    

    【讨论】:

    • 嘿!我都试过了,我得到了这个错误Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
    • @OliverD 嗯,最好制作一个博览会小吃来重现问题以进一步诊断,因为现在导入看起来是正确的,这是使用渲染(或渲染器方法)的正确方法取决于你倾向于哪个图书馆)
    猜你喜欢
    • 2017-07-05
    • 1970-01-01
    • 2016-09-14
    • 2020-03-23
    • 2018-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多