Testing helpers for use with React's shallowRender test utils.
The branch you are viewing is for a currently unreleased version, the currently released version can be found on the
pre-1.0
branch.
- Install
- Quick Start
- Usage
- API Docs
- Troubleshooting
npm install skin-deep
This lib works on both React 0.13 and React 0.14+. Because it uses some tools that changed between these versions, it cannot depend on them directly via package.json
. When using React 0.14, you'll need to add react-addons-test-utils
into your project's dependencies yourself.
var React = require('react');
var MyComponent = React.createClass({
displayName: 'MyComponent',
render: function() {
return (
<ul>
<li><a href="/">Home</a></li>
<li><a href="/abc">Draw</a></li>
<li><a href="/def">Away</a></li>
</ul>
)
}
});
var assert = require('assert');
var sd = require('skin-deep');
var tree = sd.shallowRender(<MyComponent />);
var homeLink = tree.subTree('a', { href: '/' });
assert.equal(homeLink.type, 'a');
assert.equal(homeLink.props.href, '/');
assert.equal(homeLink.text(), 'Home');
TODO: actually make these changes
subTreeLike
has been renamed tosubTree
everySubTreeLike
has been renamed toeverySubTree
- The original
subTree
has been removed,exact
can be used to get this behaviour back, but I don't recommend you do. - The original
subTreeLike
has been removed,exact
can be used to get this behaviour back, but I don't recommend you do. - removed
findNode
, usesubTree
instead - removed
textIn
, usesubTree().text()
instead - removed
fillField
, usesubTree().props.onChange
instead - removed
findComponent
, usesubTree()
instead - removed
findComponentLike
, usesubTree()
instead toString()
no-longer expands child componentsreRender()
now takes props instead of a ReactElement.
The goal of skin-deep is to provide higher level functionality built on top of the Shallow Rendering test utilities provided by React 0.13+.
By default, shallow rendering gives you a way to see what a component would render without continuing along into rendering its children. This is a very powerful baseline, but in my opinion it isn't enough to create good UI tests. You either have to assert on the whole rendered component, or manually traverse the tree like this:
assert(rendered.props.children[1].props.children[2].children, 'Click Here');
By their nature user interfaces change a lot - sometimes these changes are to behaviour, but sometimes they're simply changes to wording or minor display changes. Ideally, we'd want non-brittle UI tests which can survive these superficial changes, but still check that the application behaves as expected.
Use the shallowRender
function to get a tree
you can interact with.
var sd = require('skin-deep');
var tree = sd.shallowRender(<MyComponent />);
You can now inspect the the tree to see its contents
tree.getRenderOutput();
// -> ReactElement, same as normal shallow rendering
tree.type;
// -> The component type of the root element
tree.props;
// -> The props of the root element
The real benefits of skin deep come from the ability to extract small portions of the tree with a jQuery-esque API. You can then assert only on these sub-trees.
Extraction methods all take a CSS-esque selector as the first argument. This is commonly a component or tag name, but can also be a class or ID selector. The special value '*' can be used to match anything.
The second (optional) argument is the matcher, this can be an object to match against props, or a predicate function which will be passed each node and can decide whether to include it.
tree.subTree('Button');
// -> the first Button component
tree.everySubTree('Button');
// -> all the button components
tree.subTree('.button-primary');
// -> the first component with class of button-primary
tree.subTree('#submit-button');
// -> the first component with id of submit-button
tree.subTree('Button', { type: 'submit' });
// -> the first Button component with type=submit
tree.subTree('*', { type: 'button' });
// -> All components / elements with type=button
tree.subTree('*', function(node) { return node.props.size > 20; });
// -> All components / elements with size prop above 20
There's no DOM involved, so events could be a bit tricky - but as we're just using data, we can call functions directly!
var MyButton = React.createClass({
clicked: function(e) {
console.log(e.target.innerHTML);
},
render: function() {
return <button onClick={this.clicked}>Click {this.props.n}</button>;
}
});
var tree = sd.shallowRender(<MyButton />);
tree.subTree('button').props.onClick({
target: {
innerHTML: 'Whatever you want!'
}
});
Sometimes shallow rendering isn't enough - often you'll want to have some integration tests which can render a few layers of your application. I prefer not to have to use a full browser or jsdom for this sort of thing - so we introduced the dive
method. This allows you to move down the tree, recursively shallow rendering as needed.
var MyList = React.createClass({
render: function() {
return <ul>{[1,2,3].map(function(n) { return <MyItem n={n} />; })}</ul>;
}
});
var MyItem = React.createClass({
render: function() {
return <li><MyButton>{this.props.n}</MyButton></li>;
}
});
var MyButton = React.createClass({
render: function() {
return <button>Click {this.props.n}</button>;
}
});
var tree = sd.shallowRender(<MyList />);
var buttonElement = tree.dive(['MyItem', 'MyButton']);
assert(buttonElement.text(), 'Click 1');
TODO: flesh this out a bit more
Skin deep doesn't care which test framework you use, it just gives you the data you need to make assertions.
If you want to take this further, should be pretty simple to extend your favorite assertion library to be skin-deep aware.
As we use tend to use chai
, there's a chai
plugin bundled inside this package. You can use it via chai.use(require('skin-deep/chai'))
.
Get a tree instance by shallow-rendering a renderable ReactElement.
element {ReactElement}
- element to rendercontext {object}
- optional context
Returns tree
Access the type of the rendered root element.
Returns ReactComponent class
or string
.
Access the props of the rendered root element.
Returns object
Re-render the element with new props into the same tree as the previous render. Useful for testing how a component changes over time in response to new props from its parent.
props {object}
- the new props, which will replace the previous onescontext {object}
- optional context
Returns null
Access the textual content of the rendered root element including any text of its children. This method doesn't understand CSS, or really anything about HTML rendering, so might include text which wouldn't be displayed to the user.
Returns string
Produce a friendly JSX-esque representation of the tree
Returns string
Access the rendered component tree. This is the same result you would get using shallow rendering without skin-deep.
Returns ReactElement
Access the mounted instance of the component.
Returns object
Extract a portion of the rendered component tree. If multiple nodes match the selector, will return the first.
Returns tree
or false
Extract multiple portions of the rendered component tree.
Returns array
of tree
s
"Dive" into the rendered component tree, rendering the next level down as it goes. See Going Deeper for an example.
path {array of
Selector
s}
Returns tree
Throws if the path cannot be found.
TODO
TODO
Create a matcher which only accepts nodes that have exactly those props
passed in - no extra props.
props {object}
- to match against
Returns function
A magic value which can be used in a prop matcher that will allow any value to be matched. It will still fail if the key doesn't exist
eg.
{ abc: sd.any }
// Will match each of the following
<Component abc="1" />
<Component abc={100} />
<Component abc={function(){}} />
// but not
<Component />
<Component def="1" />
Helper function to check if a node has the HTML class specified. Exported in case you want to use this in a custom matcher.
This lib currently supports both React 0.13 and React 0.14+. If you are using a bundling tool for your test suite this will cause problems. You will need to add config to ignore the React internals for the version you are not using:
// React 0.14+ & Webpack
plugins: [
new webpack.IgnorePlugin(/ReactContext|react\/addons/),
]
// React 0.13+ & Webpack
plugins: [
new webpack.IgnorePlugin(/react-addons|react-dom/),
]
// React 0.14 & Browserify
bundle.exclude('react/lib/ReactContext');
bundle.exclude('react/addons');
// React 0.13 & Browserify
bundle.exclude('react-dom/server');
bundle.exclude('react-addons-test-utils');
// React 0.14 & jspm
jspm install npm:skin-deep -o "{map: {'react/lib/ReactContext': '@empty',
'react/addons': '@empty'}}"
// React 0.13 & jspm
jspm install npm:skin-deep -o "{map: {'react-dom/server': '@empty',
'react-addons-test-utils': '@empty'}}"