Testing
Jest
To test a component which use RNCamera, you need to create a react-native-camera.js file inside your mocks folder on the root of your project with the following content:
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: {},
};
takePictureAsync = async () => {
await timeout(2000);
return {
base64: 'base64string',
};
};
render() {
return null;
}
}
export default RNCamera;
You don't need to do anything else in your test, because Jest will use the mock in your test instead of the native module.
Example
We are going to create a component which uses RNCamera which two simple features:
- Take a photo
- Change camera between front or back
The custom component PhotoCamera is the following:
import React from 'react';
import { View, TouchableOpacity, StyleSheet, Dimensions } from 'react-native';
import { RNCamera } from 'react-native-camera';
import Icon from 'react-native-vector-icons/FontAwesome';
const styles = StyleSheet.create({
container: {
flex: 1,
display: 'flex',
flexDirection: 'column',
backgroundColor: 'black',
},
preview: {
flex: 1,
justifyContent: 'flex-end',
alignItems: 'center',
},
topButtons: {
flex: 1,
width: Dimensions.get('window').width,
alignItems: 'flex-start',
},
bottomButtons: {
flex: 1,
width: Dimensions.get('window').width,
justifyContent: 'flex-end',
alignItems: 'center',
},
flipButton: {
flex: 1,
marginTop: 20,
right: 20,
alignSelf: 'flex-end',
},
recordingButton: {
marginBottom: 10,
},
});
class PhotoCamera extends React.PureComponent {
state = {
type: RNCamera.Constants.Type.back,
};
flipCamera = () =>
this.setState({
type:
this.state.type === RNCamera.Constants.Type.back
? RNCamera.Constants.Type.front
: RNCamera.Constants.Type.back,
});
takePhoto = async () => {
const { onTakePhoto } = this.props;
const options = {
quality: 0.5,
base64: true,
width: 300,
height: 300,
};
const data = await this.camera.takePictureAsync(options);
onTakePhoto(data.base64);
};
render() {
const { type } = this.state;
return (
<View style={styles.container}>
<RNCamera
ref={cam => {
this.camera = cam;
}}
type={type}
style={styles.preview}
/>
<View style={styles.topButtons}>
<TouchableOpacity onPress={this.flipCamera} style={styles.flipButton}>
<Icon name="refresh" size={35} color="orange" />
</TouchableOpacity>
</View>
<View style={styles.bottomButtons}>
<TouchableOpacity onPress={this.takePhoto} style={styles.recordingButton}>
<Icon name="camera" size={50} color="orange" />
</TouchableOpacity>
</View>
</View>
);
}
}
export default PhotoCamera;
And here is our test to check if it renders properly:
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import { shallow, configure } from 'enzyme';
import MyPhotoCamera from './';
configure({ adapter: new Adapter() });
describe('PhotoCamera Tests', () => {
test('renders correctly', () => {
const wrapper = shallow(<MyPhotoCamera />);
expect(wrapper).toMatchSnapshot();
});
});
Also, here is the complete test of the whole component with 100% coverage:
import React from 'react';
import { TouchableOpacity } from 'react-native';
import Adapter from 'enzyme-adapter-react-16';
import { shallow, configure, mount } from 'enzyme';
import PhotoCamera from '../';
const { JSDOM } = require('jsdom');
const jsdom = new JSDOM();
const { window } = jsdom;
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === 'undefined')
.map(prop => Object.getOwnPropertyDescriptor(src, prop));
Object.defineProperties(target, props);
}
global.window = window;
global.document = window.document;
global.navigator = {
userAgent: 'node.js',
};
copyProps(window, global);
// Ignore React Web errors when using React Native
// but still show relevant errors
const suppressedErrors = /(React does not recognize the.*prop on a DOM element|Unknown event handler property|is using uppercase HTML|Received `true` for a non-boolean attribute `accessible`|The tag.*is unrecognized in this browser)|is using incorrect casing|Received `true` for a non-boolean attribute `enabled`/;
const realConsoleError = console.error; // eslint-disable-line
// eslint-disable-next-line
console.error = message => {
if (message.match(suppressedErrors)) {
return;
}
realConsoleError(message);
};
configure({ adapter: new Adapter() });
describe('PhotoCamera Tests', () => {
test('renders correctly', () => {
const wrapper = shallow(<PhotoCamera />);
expect(wrapper).toMatchSnapshot();
});
test('initial state should be back camera', () => {
const wrapper = shallow(<PhotoCamera />);
expect(wrapper.state().type).toBe('back');
});
test('should flip the camera from back to front', () => {
const wrapper = shallow(<PhotoCamera />);
expect(wrapper.state().type).toBe('back');
wrapper
.find(TouchableOpacity)
.first()
.props()
.onPress();
expect(wrapper.state().type).toBe('front');
});
test('should flip the camera from front to back if touch flip button and curren state is ', () => {
const wrapper = shallow(<PhotoCamera />);
wrapper.setState({
type: 'front',
});
wrapper.update();
wrapper
.find(TouchableOpacity)
.first()
.props()
.onPress();
expect(wrapper.state().type).toBe('back');
});
test('should have a reference to the React Native Camera module', () => {
const wrapper = mount(<PhotoCamera />);
expect(wrapper.instance().camera).toBeDefined();
});
test('test onPress functionality', async () => {
const onTakePhotoEvent = jest.fn(data => data);
const wrapper = mount(<PhotoCamera onTakePhoto={onTakePhotoEvent} />);
await wrapper
.find(TouchableOpacity)
.at(1)
.props()
.onPress();
expect(onTakePhotoEvent.mock.calls.length).toBe(1);
});
});