使用 Detox 模拟 RNCamera 不起作用,调用非模拟 impl
Mocking RNCamera with Detox does not work, non-mock impl is called
我正在尝试在 Detox 测试中模拟 RNCamera。但是,模拟相机不会覆盖真实的实现。
我关注了these instructions and incorporated advice these posts。
结果是:
- 模拟相机 class 文件本身 被 执行(打印顶级日志记录)
- 模拟相机的 takePictureAsync 和渲染函数未被调用
- 使用模拟器的默认摄像头
我也试过:
RN_SRC_EXT=e2e react-native run-android
,然后detox test
- 在最佳正文中添加
"test-runner": "jest" and calling
jest.mock`
- 将
RN_SRC_EXT=e2e
替换为 RN_SRC_EXT=e2e.js
- 清理:
rm -rf node_modules; yarn install
等
我的应用布局是这样的(排除非测试相关内容):
package.json
:
{
...
"dependencies": {
...
"@types/jest": "^24.0.21",
"react": "16.9.0",
"react-dom": "latest",
"react-native": "0.61.1",
"react-native-camera": "^3.6.0",
},
"devDependencies": {
"@babel/core": "latest",
"@babel/preset-env": "latest",
"@babel/register": "latest",
"@babel/preset-react": "latest",
"@react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.0",
"detox": "^14.5.1",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"metro-react-native-babel-preset": "^0.56.0",
"mocha": "^6.2.2",
"react-test-renderer": "16.9.0"
},
"detox": {
"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
}
}
}
}
e2e/config.js
:
require('@babel/register')({
//cache: true,
presets: [require('metro-react-native-babel-preset')],
plugins: [require('@babel/plugin-transform-runtime').default],
only: ['./e2e', './js'],
ignore: ['node_modules']
});
e2e/config.json
:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
e2e/mocha.opts
:
--recursive
--timeout 300000
--bail
--file e2e/init.js
--require e2e/config.js
--require e2e/config.json
--file e2e/react-native-camera.e2e.js
metro.config.js
:
const defaultSourceExts = require('metro-config/src/defaults/defaults').sourceExts
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
sourceExts: process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
: defaultSourceExts
}
};
// printed as: module.exports.resolver from e2e { sourceExts: [ 'e2e', 'js', 'json', 'ts', 'tsx' ] }
console.log("module.exports from e2e", module.exports);
e2e/config.json
:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
// tried with and without this line
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
test.spec.js
describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should blah blah balh', async () => {
// test implementation
});
});
init.js
:
const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/mocha/adapter');
before(async () => {
await detox.init(config);
});
beforeEach(async function() {
await adapter.beforeEach(this);
});
afterEach(async function() {
await adapter.afterEach(this);
});
after(async () => {
await detox.cleanup();
});
e2e/react-native-camera.e2e.js
:(来自 here)
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
// This IS printed on detox test -c android.emu.debug.
console.log("executing react-native-camera-e2e.js");
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
};
takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // Never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
// This is never printed.
console.log("mock RNCamera render()");
return null;
}
}
export const takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
export default RNCamera;
用 detox 模拟和用 Jest 模拟是两回事。
为了能够模拟带有 detox 的第三方模块,您可以创建一个代理组件提供程序来决定使用什么组件。这样您就可以加载模拟版本进行排毒测试。
在您的情况下,您可以创建一个 CameraProvider.js,它只会导出真实的 RNCamera:
export { RNCamera } from 'react-native-camera';
然后您将需要创建模拟版本 CameraProvider。e2e.js 与之前的文件一起使用 detox 进行端到端测试:
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
AutoFocus: { on: {} },
WhiteBalance: { auto: {} },
};
takePictureAsync = async () => {
console.log('mock RNCamera takePictureAsync');
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
console.log('mock RNCamera render()');
return null;
}
}
最后,如果您在 react-native 项目的根文件夹中有 correctly configured metro 捆绑器,您应该能够构建您的应用程序或使用 RN_SRC_EXT=e2e.js env 变量让 metro bundler 知道它应该替换哪些文件扩展名。
Gomino 的答案是正确的,但缺少您需要从被测代码中的代理组件 导入的答案。
在您的组件中执行此操作:
import { RNCamera } from '../proxies/RNCamera';
而不是
import { RNCamera } from 'react-native-camera';
然后您需要确保捆绑器是使用 RN_SRC_EXT=e2e.js 环境变量启动的。所以我的排毒测试脚本(在 package.json 中)看起来像这样:
"start-detox-metro": "RN_SRC_EXT=e2e.js react-native start",
"detox-build": "detox build --configuration android.emu.debug",
"detox test": "detox test --configuration android.emu.debug --reuse",
运行 脚本按此顺序排列。
metro bundler 将处理交换 .e2e.js 文件以代替原始代理文件(但仅当设置了环境变量“RN_SRC_EXT=e2e.js”时)。
我正在尝试在 Detox 测试中模拟 RNCamera。但是,模拟相机不会覆盖真实的实现。
我关注了these instructions and incorporated advice
结果是:
- 模拟相机 class 文件本身 被 执行(打印顶级日志记录)
- 模拟相机的 takePictureAsync 和渲染函数未被调用
- 使用模拟器的默认摄像头
我也试过:
RN_SRC_EXT=e2e react-native run-android
,然后detox test
- 在最佳正文中添加
"test-runner": "jest" and calling
jest.mock` - 将
RN_SRC_EXT=e2e
替换为RN_SRC_EXT=e2e.js
- 清理:
rm -rf node_modules; yarn install
等
我的应用布局是这样的(排除非测试相关内容):
package.json
:
{
...
"dependencies": {
...
"@types/jest": "^24.0.21",
"react": "16.9.0",
"react-dom": "latest",
"react-native": "0.61.1",
"react-native-camera": "^3.6.0",
},
"devDependencies": {
"@babel/core": "latest",
"@babel/preset-env": "latest",
"@babel/register": "latest",
"@babel/preset-react": "latest",
"@react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.0",
"detox": "^14.5.1",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"metro-react-native-babel-preset": "^0.56.0",
"mocha": "^6.2.2",
"react-test-renderer": "16.9.0"
},
"detox": {
"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
}
}
}
}
e2e/config.js
:
require('@babel/register')({
//cache: true,
presets: [require('metro-react-native-babel-preset')],
plugins: [require('@babel/plugin-transform-runtime').default],
only: ['./e2e', './js'],
ignore: ['node_modules']
});
e2e/config.json
:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
e2e/mocha.opts
:
--recursive
--timeout 300000
--bail
--file e2e/init.js
--require e2e/config.js
--require e2e/config.json
--file e2e/react-native-camera.e2e.js
metro.config.js
:
const defaultSourceExts = require('metro-config/src/defaults/defaults').sourceExts
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
sourceExts: process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
: defaultSourceExts
}
};
// printed as: module.exports.resolver from e2e { sourceExts: [ 'e2e', 'js', 'json', 'ts', 'tsx' ] }
console.log("module.exports from e2e", module.exports);
e2e/config.json
:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
// tried with and without this line
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
test.spec.js
describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should blah blah balh', async () => {
// test implementation
});
});
init.js
:
const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/mocha/adapter');
before(async () => {
await detox.init(config);
});
beforeEach(async function() {
await adapter.beforeEach(this);
});
afterEach(async function() {
await adapter.afterEach(this);
});
after(async () => {
await detox.cleanup();
});
e2e/react-native-camera.e2e.js
:(来自 here)
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
// This IS printed on detox test -c android.emu.debug.
console.log("executing react-native-camera-e2e.js");
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
};
takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // Never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
// This is never printed.
console.log("mock RNCamera render()");
return null;
}
}
export const takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
export default RNCamera;
用 detox 模拟和用 Jest 模拟是两回事。
为了能够模拟带有 detox 的第三方模块,您可以创建一个代理组件提供程序来决定使用什么组件。这样您就可以加载模拟版本进行排毒测试。
在您的情况下,您可以创建一个 CameraProvider.js,它只会导出真实的 RNCamera:
export { RNCamera } from 'react-native-camera';
然后您将需要创建模拟版本 CameraProvider。e2e.js 与之前的文件一起使用 detox 进行端到端测试:
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
AutoFocus: { on: {} },
WhiteBalance: { auto: {} },
};
takePictureAsync = async () => {
console.log('mock RNCamera takePictureAsync');
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
console.log('mock RNCamera render()');
return null;
}
}
最后,如果您在 react-native 项目的根文件夹中有 correctly configured metro 捆绑器,您应该能够构建您的应用程序或使用 RN_SRC_EXT=e2e.js env 变量让 metro bundler 知道它应该替换哪些文件扩展名。
Gomino 的答案是正确的,但缺少您需要从被测代码中的代理组件 导入的答案。
在您的组件中执行此操作:
import { RNCamera } from '../proxies/RNCamera';
而不是
import { RNCamera } from 'react-native-camera';
然后您需要确保捆绑器是使用 RN_SRC_EXT=e2e.js 环境变量启动的。所以我的排毒测试脚本(在 package.json 中)看起来像这样:
"start-detox-metro": "RN_SRC_EXT=e2e.js react-native start",
"detox-build": "detox build --configuration android.emu.debug",
"detox test": "detox test --configuration android.emu.debug --reuse",
运行 脚本按此顺序排列。
metro bundler 将处理交换 .e2e.js 文件以代替原始代理文件(但仅当设置了环境变量“RN_SRC_EXT=e2e.js”时)。