私たちのリポジトリ (作業中なので、残念ながらリンクを提供することはできません) はかなり大きくて複雑なので、私の問題を説明するのは少し難しいですが、最善を尽くします。まず第一に、問題:
問題
実行する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
エラー メッセージは、モノレポ内から (@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
コンポーネント テスト ( jest-nativeおよびRN Testing Libraryを使用) と、リポジトリ内の単純な単体テストの両方があります。detox
ディレクトリの存在から推測できるように、テストもありますがe2e
、現時点では重要ではありません。
すべてのコード (テスト コードとアプリ コード) は TypeScript で記述され、React Native 部分については、RN チームが推奨するようにすべて Babel 経由でコンパイルされます。ただし、テストのために、コードを CommonJS にコンパイルするようにts-jestjest
を構成しました。TypeScript コードをコンパイルし、すべての JavaScript コードttypescript
で使用するように特別に構成されています。babel-jest
すべてがどのように構成されているかを示すために、最も重要な構成をここに貼り付けます。
設定
Jest設定
Expo の unimodulesを使用するためjest-expo
、Expo 側の jest-testing を適切にセットアップするインストールも行いました。これ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);
};
TypeScript 構成
./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
!あなたが私を助けてくれることを願っています。ありがとう!