我正在用TypeScript构建一个React Native应用程序。我在单元测试中使用Jest和Enzyme。
我最近使用this package将QRCode Scanner添加到了我的应用中。它取决于this camera package。添加软件包后,我为使用该扫描仪的新组件编写完测试(我们执行TDD)后,我突然注意到某些测试失败:
● Test suite failed to run
TypeError: Cannot read property 'Aspect' of undefined
at Object.<anonymous> (node_modules/react-native-camera/src/Camera.js:386:113)
at Object.<anonymous> (node_modules/react-native-camera/src/index.js:1:413)
at Object.<anonymous> (node_modules/react-native-qrcode-scanner/index.js:19:26)
与往常一样,我在错误中进行了搜索,并找到了两个修复程序。
在每次测试中模拟会引发此错误的相机,如下所示:
jest.mock("react-native-camera", () => "Camera");
像这样在项目级别__mocks__/
文件夹中模拟摄像头模块:
const Camera = {}
export default Camera;
当我这样做时,有关“方面”的错误就消失了。但是现在,Jest开始在涉及节点模块的所有测试中抱怨import
。
● Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
/Users/my/folders/node_modules/react-navigation-stack/dist/navigators/createContainedStackNavigator.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){import { createNavigationContainer } from 'react-navigation';
^^^^^^
SyntaxError: Unexpected token import
9 | import ScannerScreen from "../screens/Scanner";
10 |
> 11 | const HomeStack = createStackNavigator({ HomeScreen });
| ^
12 | const ScannerStack = createStackNavigator({ ScannerScreen });
13 |
14 | const MainDrawer = createDrawerNavigator({ HomeStack, ScannerStack });
at ScriptTransformer._transformAndBuildScript (node_modules/jest-runtime/build/script_transformer.js:403:17)
at Object.get createStackNavigator [as createStackNavigator] (node_modules/react-navigation-stack/dist/index.js:8:12)
at Object.get createStackNavigator [as createStackNavigator] (node_modules/react-navigation/src/react-navigation.js:29:45)
at Object.<anonymous> (app/navigation/Navigator.ts:11:19)
第一个奇怪的事情是 这以前没有发生过!的意思是,在我添加此模拟程序和带有其依赖项的QR Code Scanner程序包之前(本机-相机),所有测试均正常!对于Jest来说,转换导入不是问题。
好的,所以我再次搜索了一下,发现有些人可以使用"transform-ignore-patterns"
来解决此问题。
这是我尝试过的配置:
transformIgnorePatterns: [
"<rootDir>/node_modules/react-native/.+"
]
"transformIgnorePatterns": [
"node_modules/(?!react-native|react-navigation)/"
],
这两个都不起作用。他们导致测试开始之前需要20秒钟(当我只测试一个组件时)或几分钟(当我使用npm test
同时运行所有测试时)之间的时间。然后他们将立即失败:
FAIL app/components/LoginForm/LoginForm.test.tsx
● Test suite failed to run
The component for route 'LoginScreen' must be a React component. For example:
import MyScreen from './MyScreen';
...
LoginScreen: MyScreen,
}
You can also use a navigator:
import MyNavigator from './MyNavigator';
...
LoginScreen: MyNavigator,
}
14 | const MainDrawer = createDrawerNavigator({ HomeStack, ScannerStack });
15 |
> 16 | const RootSwitch = createSwitchNavigator(
| ^
17 | { MainDrawer, LoadingScreen, LoginScreen },
18 | { initialRouteName: "LoadingScreen" }
19 | );
at node_modules/react-navigation/src/routers/validateRouteConfigMap.js:24:13
at Array.forEach (<anonymous>)
at validateRouteConfigMap (node_modules/react-navigation/src/routers/validateRouteConfigMap.js:14:14)
at _default (node_modules/react-navigation/src/routers/SwitchRouter.js:22:39)
at createSwitchNavigator (node_modules/react-navigation/src/navigators/createSwitchNavigator.js:7:42)
at Object.SwitchNavigator (node_modules/react-navigation/src/navigators/createContainedSwitchNavigator.js:5:54)
at Object.<anonymous> (app/navigation/Navigator.ts:16:20)
这是怎么回事?为什么Jest突然坏了?在react-native-camera
出现之前,导入工作正常。
添加的信息:
这是我的配置文件。
package.json
:
"jest": {
"preset": "react-native",
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js",
"^.+\\.tsx?$": "ts-jest"
},
"globals": {
"ts-jest": {
"tsConfigFile": "tsconfig.jest.json"
}
},
"transformIgnorePatterns": [
"node_modules/(?!react-native|react-navigation)/"
],
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": [
"ts",
"tsx",
"js",
"jsx",
"json",
"node"
],
"modulePaths": [
"<rootDir>"
],
"setupFiles": [
"./tests/setup.js"
]
}
tsconfig.jest.json
:
{
"extends": "./tsconfig",
"compilerOptions": {
"jsx": "react",
"module": "commonjs"
}
}
tsconfig.json
:
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"jsx": "react-native",
"lib": ["es2017"],
"moduleResolution": "node",
"noEmit": true,
"strict": true,
"strictPropertyInitialization": false,
"target": "esnext"
},
"exclude": ["node_modules"]
}
tests/setup.js
:
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { NativeModules } from "react-native";
jest.mock("react-native-device-info", () => {
return {
getDeviceName: jest.fn(() => "device name"),
getBrand: jest.fn(() => "android"),
getDeviceId: jest.fn(() => "device-id-123")
};
});
NativeModules.ReactLocalization = {
language: "en"
};
global.fetch = require("jest-fetch-mock"); // eslint-disable-line no-undef
Enzyme.configure({ adapter: new Adapter() });