3

所以,我的问题有点难以描述,因为我们的存储库(它正在工作,所以很遗憾我不能只提供它的链接)相当大而且很复杂,但我会尽力而为。首先,问题:

问题

我运行jest,结果如下所示:

FAIL  packages/components/src/PlusMinus/index.test.tsx
  ● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'spacing')

      15 |     justifyContent: 'center',
      16 |     alignItems: 'center',
    > 17 |     paddingRight: Metrics.spacing.xxs,
         |                           ^
      18 |   },
      19 | });
      20 |

      at Object.<anonymous> (packages/components/src/IconWithBadge/styles.ts:17:27)
      at Object.<anonymous> (packages/components/src/IconWithBadge/index.tsx:9:1)

Test Suites: 1 failed, 1 total

对于从我们的 monorepo 中导入任何内容(通过@packages/别名)的所有文件测试,错误消息都是相同的——包括。完全相同的组件,IconWithBadge显然无法导入Metrics模块。有关我们当前所有测试套件的完整输出,请参见下文。

PASS  packages/package1/src/utils/index.test.ts
  Testing *****function()
    √ Normal items (5 ms)
    √ Other items (2 ms)
    √ Combo items (5 ms)
  Testing *****function()
    √ Multiple identical items (1 ms)
    √ Multiple different items, one theme each (1 ms)
    √ Multiple different items, with a unique theme for each entry (3 ms)

 PASS  packages/utils/src/transformations/transformations.test.ts
  test transformation helpers
    √ spliceImmutably() (4 ms)
    √ momentToShortenedDateString() (4 ms)
    √ sortByProperty() (3 ms)
    √ roundByPrecision() (1 ms)
    √ parseDotNotation()
    √ versionStringToArray() (1 ms)
    √ capitalizeString() (1 ms)
    √ normalizeString() (2 ms)
    √ normalizeUmlauts1() (3 ms)
    √ normalizeUmlauts2() (2 ms)

 FAIL  packages/utils/src/validation/validation.test.ts
  ● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'spacing')

      15 |     justifyContent: 'center',
      16 |     alignItems: 'center',
    > 17 |     paddingRight: Metrics.spacing.xxs,
         |                           ^
      18 |   },
      19 | });
      20 |

      at Object.<anonymous> (packages/components/src/IconWithBadge/styles.ts:17:27)
      at Object.<anonymous> (packages/components/src/IconWithBadge/index.tsx:9:1)

 FAIL  packages/customer/src/screens/Profile/components/CustomerCard/components/PinInput/index.test.tsx
  ● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'spacing')

      15 |     justifyContent: 'center',
      16 |     alignItems: 'center',
    > 17 |     paddingRight: Metrics.spacing.xxs,
         |                           ^
      18 |   },
      19 | });
      20 |

      at Object.<anonymous> (packages/components/src/IconWithBadge/styles.ts:17:27)
      at Object.<anonymous> (packages/components/src/IconWithBadge/index.tsx:9:1)

 FAIL  packages/package2/src/screens/Screen1/components/Component1/index.test.tsx
  ● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'spacing')

      15 |     justifyContent: 'center',
      16 |     alignItems: 'center',
    > 17 |     paddingRight: Metrics.spacing.xxs,
         |                           ^
      18 |   },
      19 | });
      20 |

      at Object.<anonymous> (packages/components/src/IconWithBadge/styles.ts:17:27)
      at Object.<anonymous> (packages/components/src/IconWithBadge/index.tsx:9:1)

 FAIL  packages/components/src/PlusMinus/index.test.tsx
  ● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'spacing')

      15 |     justifyContent: 'center',
      16 |     alignItems: 'center',
    > 17 |     paddingRight: Metrics.spacing.xxs,
         |                           ^
      18 |   },
      19 | });
      20 |

      at Object.<anonymous> (packages/components/src/IconWithBadge/styles.ts:17:27)
      at Object.<anonymous> (packages/components/src/IconWithBadge/index.tsx:9:1)

 FAIL  packages/package3/src/SomeService/utils/index.test.ts
  ● Test suite failed to run

    TypeError: Cannot read properties of undefined (reading 'spacing')

      15 |     justifyContent: 'center',
      16 |     alignItems: 'center',
    > 17 |     paddingRight: Metrics.spacing.xxs,
         |                           ^
      18 |   },
      19 | });
      20 |

      at Object.<anonymous> (packages/components/src/IconWithBadge/styles.ts:17:27)
      at Object.<anonymous> (packages/components/src/IconWithBadge/index.tsx:9:1)

Test Suites: 5 failed, 2 passed, 7 total
Tests:       16 passed, 16 total
Snapshots:   0 total
Time:        8.832 s

对我来说,这似乎是相对明显的,这一定是某种编译错误,因为这里需要注意的重要一点是:

在 React Native 上下文中,所有这些代码都可以正常工作!

意思是,像往常一样通过命令构建和运行应用程序时react-native,一切正常,我没有收到任何错误。只有在jest测试的上下文中,我才会收到此错误。

另外,假设我删除了Metricsfrom的所有实例IconWithBadgejest再次运行。Metrics然后,它会奇迹般地找到另一个导入的文件undefined

现在,为了让您大致了解我们的存储库的结构和配置方式,让我们看一下环境。

环境

我们有一个类似于以下结构的 monorepo:

.
├── app-directory
│   ├── android
│   ├── ios
│   ├── src
│   │   ├── navigation
│   │   │   └── ... all the react-navigation navigators
│   │   └── App.tsx
│   ├── babel.config.js
│   ├── index.js
│   ├── metro.config.js
│   ├── package.json
│   ├── react-native.config.js
│   └── tsconfig.json
├── e2e
│   ├── tests
│   │   └── testSomething.e2e.ts
│   ├── config.json
│   └── environment.js
├── packages
│   ├── package1
│   │   ├── src
│   │   ├── package.json
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   ├── package2
│   │   ├── src
│   │   ├── package.json
│   │   ├── tsconfig.build.json
│   │   └── tsconfig.json
│   └── ...more packages, all look similar
├── .eslintignore
├── .eslintrc.yml
├── .gitattributes
├── .gitignore
├── .babel.config.js
├── detox.config.js
├── get-babel-config.js
├── jest.config.js
├── package.json
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── yarn.lock

我们在 repo 中有组件测试(通过jest-nativeRN 测试库)和简单的单元测试。我们也有detox测试,你可以从目录的存在中推断出来e2e,但目前这并不重要。

所有代码(测试应用程序代码)都是用 TypeScript 编写的,对于 React Native 部分,它都是通过 Babel 编译的,这是RN 团队推荐的。然而,对于测试,我已配置ts-jest将代码编译为 CommonJS,这是jest. 它专门配置为编译 TypeScript 代码ttypescriptbabel-jest用于所有 JavaScript 代码。

为了向您展示它是如何配置的,我将在此处粘贴我最重要的配置:

配置

笑话配置

由于我们使用Expo 的 unimodules,我们也jest-expo安装了,它正确设置了 jest-testing 的 Expo 端。考虑到这ts-jest一点,我们jest.config.js看起来像这样:

const cloneDeep = require('lodash/cloneDeep');
const expoPreset = cloneDeep(require('jest-expo/jest-preset'));
const tsjPreset = cloneDeep(require('ts-jest/presets').jsWithBabel);

delete expoPreset.transform['^.+\\.(js|ts|tsx)$'];

/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
const config = {
  bail: true,
  globals: {
    'ts-jest': {
      compiler: 'ttypescript',
      tsconfig: 'tsconfig.spec.json',
      isolatedModules: true,
      diagnostics: {
        warnOnly: true,
      },
    },
  },
  moduleDirectories: [
    'node_modules',
    // add the directory with the test-utils.js file, for example:
    'test-utils', // a utility folder
    'packages', // packages folder
    __dirname, // the root directory
  ],
  moduleFileExtensions: ['ts', 'tsx', 'js'],
  moduleNameMapper: {
    '^@packages/utils/(.*)$': '<rootDir>/packages/utils/src/$1',
    '^@packages/services/(.*)$': '<rootDir>/packages/services/src/$1',
    '^@packages/(.*)$': '<rootDir>/packages/$1/src',
    '^test-utils$': '<rootDir>/test-utils',
  },
  rootDir: process.cwd(),
  setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
  testTimeout: 10000,
  transform: Object.assign(tsjPreset.transform, expoPreset.transform),
  transformIgnorePatterns: [
    `${
      expoPreset.transformIgnorePatterns[0].split('svg)')[0]
    }svg|@packages/*|react-native-ultimate-config|react-native-calendars|react-native-permissions|react-native-swipe-gestures|react-native-iphone-x-helper|react-native-webview|react-native-reanimated|react-native-qrcode-svg|react-native-linear-gradient|@react-native-masked-view/*|react-native-svg-path-gradient|react-native-safe-area-context|redux-middleware-flipper|react-native-flipper)`,
  ],
  verbose: true,
};

module.exports = Object.assign(expoPreset, Object.assign(tsjPreset, config));

通天塔配置

./get-babel-config.js

/**
 * @param {import('@types/babel__core').ConfigAPI} api
 * @param {boolean} isRoot
 * @returns {import('@types/babel__core').TransformOptions}
 */
module.exports = function (api, isRoot = true) {
  const relativeRoot = isRoot ? './' : '../';
  /** @type {import('@types/babel__core').TransformOptions} */
  const config = {
    presets: [
      ['@babel/preset-env', {targets: 'defaults', loose: true}],
      ['module:metro-react-native-babel-preset', {useTransformReactJSXExperimental: true}],
    ],
    sourceMaps: 'inline',
  };

  config.plugins = [
    'lodash',
    [
      'module-resolver',
      {
        root: [relativeRoot],
        extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
        alias: {
          '^@packages/services/(.+)': `${relativeRoot}packages/services/src/\\1`,
          '^@packages/utils/(.+)': `${relativeRoot}packages/utils/src/\\1`,
          '^@packages/(.+)': `${relativeRoot}packages/\\1/src`,
          '^test-utils': `${relativeRoot}test-utils`,
        },
      },
    ],
    ['@babel/plugin-transform-react-jsx', {runtime: 'automatic'}],
  ];

  if (api.env('production')) {
    config.plugins.push([
      'transform-remove-console',
      {exclude: ['error', 'warn', 'info', 'time', 'timeEnd']},
    ]);
  }

  return config;
};

./app-directory/babel.config.js

const getConfig = require('../get-babel-config');

module.exports = function (api) {
  return getConfig(api, false);
};

./babel.config.js

const getConfig = require('./get-babel-config');

module.exports = function (api) {
  return getConfig(api);
};

打字稿配置

./tsconfig.json

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ES6",
    "allowJs": false,
    "jsx": "react-native",
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "composite": true,
    "importHelpers": true,
    "isolatedModules": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "skipLibCheck": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@packages/*": [
        "./packages/*/src"
      ],
      "@packages/services/*": [
        "./packages/services/src/*"
      ],
      "@packages/utils/*": [
        "./packages/utils/src/*"
      ]
    },
    "types": [
      "node",
      "jest",
      "detox"
    ],
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "plugins": [
      {
        "transform": "@zerollup/ts-transform-paths"
      },
      {
        "transform": "typescript-transform-react-jsx-source"
      }
    ]
  },
  "exclude": [
    "node_modules",
    "**/*.js"
  ]
}

./tsconfig.spec.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "target": "ES5",
    "jsx": "react-jsx",
    "paths": {
      "@packages/*": [
        "./packages/*/src"
      ],
      "@packages/services/*": [
        "./packages/services/src/*"
      ],
      "@packages/utils/*": [
        "./packages/utils/src/*"
      ],
      "test-utils": [
        "./test-utils"
      ]
    }
  }
}

打包 JSON

./package.json

{
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*",
      "app-directory"
    ]
  },
  "license": "MIT",
  "scripts": {
    "lint": "eslint \"**/*.{js,ts,tsx}\"",
    "typescript": "tsc",
    "test:jest": "jest",
    "test:jest:plusminus-only": "jest ./packages/components/src/PlusMinus/index.test.tsx",
    "test:detox-ios": "detox build -c ios && detox test -c ios -l verbose",
    "test:detox-ios-ci": "detox build -c ios && detox test -c ios --workers 2 --headless --record-logs all --cleanup",
    "test:detox-android": "detox build -c android && detox test -c android -l verbose --record-logs all",
    "test:detox-android-ci": "detox build -c android && detox test -c android --workers 2 --headless --record-logs all --cleanup",
    "publish": "lerna publish",
    "prerelease": "lerna run clean",
    "in-app": "yarn --cwd app-directory",
    "postinstall": "patch-package"
  },
  "devDependencies": {
    "@babel/cli": "^7.15.4",
    "@babel/core": "^7.15.5",
    "@babel/preset-env": "^7.15.6",
    "@babel/runtime": "^7.15.4",
    "@packages/build-tools": "^1.0.31",
    "@react-native-community/eslint-config": "^3.0.1",
    "@testing-library/jest-native": "^4.0.2",
    "@testing-library/react-native": "^7.2.0",
    "@types/detox": "^17.14.2",
    "@types/i18n-js": "^3.8.2",
    "@types/jest": "^27.0.1",
    "@types/lodash": "^4.14.173",
    "@types/node": "^16.9.2",
    "@types/react": "^17.0.21",
    "@types/react-native": "^0.65.0",
    "@types/react-native-calendars": "^1.1264.2",
    "@types/react-native-vector-icons": "^6.4.8",
    "@types/react-test-renderer": "^17.0.1",
    "@types/react-redux": "^7.1.18",
    "@typescript-eslint/eslint-plugin": "^4.31.1",
    "@typescript-eslint/parser": "^4.31.1",
    "@zerollup/ts-transform-paths": "^1.7.18",
    "babel-jest": "^27.2.0",
    "babel-plugin-lodash": "^3.3.4",
    "babel-plugin-module-resolver": "^4.1.0",
    "babel-plugin-transform-remove-console": "^6.9.4",
    "detox": "^18.20.3",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-detox": "^1.0.0",
    "eslint-plugin-import": "^2.24.2",
    "eslint-plugin-jest": "^24.4.2",
    "eslint-plugin-prettier": "^4.0.0",
    "eslint-plugin-testing-library": "^4.12.2",
    "jest": "^27.2.0",
    "jest-expo": "^42.1.0",
    "lerna": "^4.0.0",
    "metro-react-native-babel-preset": "^0.66.2",
    "patch-package": "^6.4.7",
    "postinstall-postinstall": "^2.1.0",
    "prettier": "^2.4.1",
    "react-native-codegen": "^0.0.7",
    "react-test-renderer": "^17.0.2",
    "run-script-os": "^1.1.6",
    "sanitize-filename": "^1.6.3",
    "ts-jest": "^27.0.5",
    "tslib": "^2.3.1",
    "ttypescript": "^1.5.12",
    "typescript": "^4.4.3",
    "typescript-transform-react-jsx-source": "^2.0.0"
  },
  "prettier": {
    "tabWidth": 2,
    "bracketSpacing": false,
    "jsxBracketSameLine": true,
    "singleQuote": true,
    "trailingComma": "all",
    "printWidth": 100,
    "endOfLine": "lf"
  },
  "resolutions": {
    "jest-expo/babel-jest": "^27.2.0",
    "jest-expo/jest": "^27.2.0",
    "jest-expo/react-test-renderer": "^17.0.2"
  }
}

./app-directory/package.json

{
  "private": true,
  "scripts": {
    "start-server": "react-native start --reset-cache",
    "doctor": "react-native doctor",
    "run-app": "react-native run-android --variant=devdemoDebug",
    "run-app:release": "react-native run-android --variant=devdemoRelease",
    "install-pods": "rm -rf ios/Pods && cd ios && pod install --clean-install && cd ..",
    "test": "jest --passWithNoTests"
  },
  "dependencies": {
    "@packages/authentication": "^1.0.31",
    "@packages/package1": "^1.0.31",
    "@packages/package2": "^1.0.31",
    "@packages/components": "^1.0.31",
    "@packages/customer": "^1.0.31",
    "@packages/errors": "^1.0.31",
    "@packages/package3": "^1.0.31",
    "@packages/package4": "^1.0.31",
    "@packages/package5": "^1.0.31",
    "@packages/hooks": "^1.0.31",
    "@packages/package6": "^1.0.31",
    "@packages/package7": "^1.0.31",
    "@packages/package8": "^1.0.31",
    "@packages/screens": "^1.0.31",
    "@packages/services": "^1.0.31",
    "@packages/store": "^1.0.31",
    "@packages/themes": "^1.0.31",
    "@packages/utils": "^1.0.31",
    "@packages/package9": "^1.0.31",
    "@packages/package10": "^1.0.31",
    "@ctrl/tinycolor": "^3.4.0",
    "@react-native-async-storage/async-storage": "^1.15.8",
    "@react-native-clipboard/clipboard": "^1.8.4",
    "@react-native-community/blur": "^3.6.0",
    "@react-native-community/netinfo": "^6.0.2",
    "@react-native-community/viewpager": "4.x",
    "@react-native-masked-view/masked-view": "^0.2.6",
    "@react-navigation/bottom-tabs": "^5.11.15",
    "@react-navigation/drawer": "^5.12.9",
    "@react-navigation/native": "^5.9.8",
    "@react-navigation/stack": "^5.14.9",
    "@reduxjs/toolkit": "^1.6.1",
    "expo-barcode-scanner": "^10.2.2",
    "expo-local-authentication": "^11.1.1",
    "lottie-ios": "3.2.3",
    "lottie-react-native": "^4.0.3",
    "moment": "^2.29.1",
    "react": "^17.0.2",
    "react-native": "^0.65.1",
    "react-native-bootsplash": "^3.2.5",
    "react-native-camera": "3.x",
    "react-native-console-time-polyfill": "^1.2.3",
    "react-native-device-info": "^8.3.3",
    "react-native-exception-handler": "^2.10.10",
    "react-native-flipper": "^0.109.0",
    "react-native-gesture-handler": "^1.10.3",
    "react-native-inappbrowser-reborn": "^3.6.3",
    "react-native-keychain": "^7.0.0",
    "react-native-linear-gradient": "^2.5.6",
    "react-native-localize": "^2.1.4",
    "react-native-permissions": "^3.0.5",
    "react-native-reanimated": "^1.13.3",
    "react-native-restart": "^0.0.22",
    "react-native-safe-area-context": "^3.3.2",
    "react-native-screens": "^3.7.2",
    "react-native-share": "^7.1.0",
    "react-native-simple-toast": "^1.1.3",
    "react-native-svg": "^12.1.1",
    "react-native-ultimate-config": "^3.4.1",
    "react-native-unimodules": "^0.14.8",
    "react-native-url-polyfill": "^1.3.0",
    "react-native-vector-icons": "7.0.0",
    "react-native-view-shot": "^3.1.2",
    "react-native-wallet-passes": "^1.2.2",
    "react-native-webview": "11.13.0",
    "react-redux": "^7.2.5",
    "redux": "^4.1.1",
    "rn-fetch-blob": "^0.12.0"
  },
  "devDependencies": {
    "@packages/types-and-enums": "^1.0.31"
  }
}

概括

这应该是所有相关信息。如果有任何遗漏/您需要任何其他信息,请告诉我!总结一下这个问题:

为什么jest在正常开发/生产过程中完全正常工作时无法解决此包?我怎样才能解决这个问题?

在这里请教所有jest专家!我希望你能帮助我。谢谢!

4

0 回答 0