An integration test framework for React Native
Cavy
Cavy is a cross-platform integration test framework for React Native, by Pixie Labs.
This README covers installing and setting up Cavy, writing Cavy tests and FAQs. For information on how to use Cavy's command line interface, check out cavy-cli.
How does it work?
Cavy (ab)uses React ref
generating functions to give you the ability to refer
to, and simulate actions upon, deeply nested components within your
application. Unlike a tool like enzyme
which uses a simulated renderer, Cavy runs within your live application as it
is running on a host device (e.g. your Android or iOS simulator).
CLI and continuous integration
By default, Cavy outputs test results to the console when your app runs.
However, you can also run Cavy tests directly from the command line using
Cavy's own command line interface - [cavy-cli][cli]. Just set the sendReport
prop on your <Tester>
component to true
(see below).
Further details on how you can use cavy-cli to fully automate your tests with
continuous integration can be found in the [cavy-cli README][cli].
Where does it fit in?
We built Cavy because, at the time of writing, React Native had only a handful
of testing approaches available:
- Unit testing components (Jest).
- Shallow-render testing components (enzyme).
- Testing within your native environment, using native JS hooks (Appium).
- Testing completely within your native environment (XCTest).
Cavy fits in between shallow-render testing and testing within your native
environment.
Installation
To get started using Cavy, install it using yarn
:
yarn add cavy --dev
or npm
:
npm i --save-dev cavy
Usage
Check out the sample app
for example usage. Here it is running:
1. Set up the Tester
Import Tester
, TestHookStore
and your specs in your top-level JS file
(typically this is your index.{ios,android}.js
files). Instantiate a new TestHookStore
and render your app inside a Tester
.
// index.ios.js
import React, { Component } from 'react';
import { Tester, TestHookStore } from 'cavy';
import AppSpec from './specs/AppSpec';
import App from './app';
const testHookStore = new TestHookStore();
export default class AppWrapper extends Component {
render() {
return (
<Tester specs={[AppSpec]} store={testHookStore} waitTime={4000}>
<App />
</Tester>
);
}
}
Tester props
Prop | Type | Description | Default |
---|---|---|---|
specs (required) | Array | Your spec functions | - |
store (required) | TestHookStore | The newly instantiated TestHookStore component | - |
waitTime | Integer | Time in milliseconds that your tests should wait to find a component | 2000 |
startDelay | Integer | Time in milliseconds before test execution begins | 0 |
clearAsyncStorage | Boolean | If true, clears AsyncStorage between each test e.g. to remove a logged in user | false |
sendReport | Boolean | If true, Cavy sends a report to [cavy-cli][cli] | false |
2. Hook up components
Add a test hook to any components you want to test by adding a ref and using the
generateTestHook
function. Then export a hooked version of the parent component.
generateTestHook
takes a string as its first argument - this is the
identifier used in tests. It takes an optional second argument in case
you also want to set your own ref generating function.
// src/Scene.js
import React, { Component } from 'react';
import { TextInput } from 'react-native';
import { hook } from 'cavy';
class Scene extends Component {
render() {
return (
<View>
<TextInput
ref={this.props.generateTestHook('Scene.TextInput')}
onChangeText={...}
/>
</View>
);
}
}
const TestableScene = hook(Scene);
export default TestableScene;
Note on functional components: Functional components cannot be assigned a ref since they don't have instances. We suggest using Recompose's toClass
helper function to convert it to a class component first.
3. Write test cases
Write your spec functions referencing your hooked-up components. See below for a list of currently available spec
helper functions.
// specs/AppSpec.js
export default function(spec) {
spec.describe('My feature', function() {
spec.it('works', async function() {
await spec.fillIn('Scene.TextInput', 'some string')
await spec.press('Scene.button');
await spec.exists('NextScene');
});
});
}
Congratulations! You are now all set up to start testing your app with Cavy. Your tests will run automatically when you run your app.
Apps that use native code
If you're not using [Create React Native App][crna], you'll need to register
your AppWrapper
as the main entry point with AppRegistry
instead of your
current App
component:
AppRegistry.registerComponent('AppWrapper', () => AppWrapper);
Available spec helpers
Function | Description |
---|---|
fillIn(identifier, str) |
Fills in the identified component with the string Component must respond to onChangeText |
press(identifier) |
Presses the identified component Component must respond to onPress |
pause(integer) |
Pauses the test for this length of time (milliseconds) Useful if you need to allow time for a response to be received before progressing |
exists(identifier) |
Returns true if the component can be identified (i.e. is currently on screen) |
notExists(identifier) |
As above, but checks for the absence of the component |
findComponent(identifier) |
Returns the identified component Can be used if your component doesn't respond to either onChangeText or onPress For example: const picker = await spec.findComponent('Scene.modalPicker'); picker.open(); |
Writing your own spec helpers
Want to test something not included above? Write your own spec helper function!
Your function will need to be asynchronous and should throw an error in situations
where you want the test to fail. For example, the following tests whether a <Text>
component displays the correct text.
// specs/helpers.js
export async function containsText(component, text) {
if (!component.props.children.includes(text)) {
throw new Error(`Could not find text ${text}`);
};
}
// specs/AppSpec.js
import { containsText } from './helpers';
export default function(spec) {
spec.describe('Changing the text', function() {
spec.it('works', async function() {
await spec.press('Scene.button');
const text = await spec.findComponent('Scene.text');
await containsText(text, 'you pressed the button');
});
});
}