所以,我的问题有点难以描述,因为我们的存储库(它正在工作,所以很遗憾我不能只提供它的链接)相当大而且很复杂,但我会尽力而为。首先,问题:
问题
我运行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
测试的上下文中,我才会收到此错误。
另外,假设我删除了Metrics
from的所有实例IconWithBadge
并jest
再次运行。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-native和RN 测试库)和简单的单元测试。我们也有detox
测试,你可以从目录的存在中推断出来e2e
,但目前这并不重要。
所有代码(测试和应用程序代码)都是用 TypeScript 编写的,对于 React Native 部分,它都是通过 Babel 编译的,这是RN 团队推荐的。然而,对于测试,我已配置ts-jest将代码编译为 CommonJS,这是jest
. 它专门配置为编译 TypeScript 代码ttypescript
并babel-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
专家!我希望你能帮助我。谢谢!