Plugin for unexpected, to allow for assertions on the React.js virtual DOM, and the shallow and test renderers
npm install unexpected-react


Plugin for unexpected to allow for testing the full virtual DOM, and
against the shallow renderer (replaces unexpected-react-shallow)
See the blog post for an introduction: https://medium.com/@bruderstein/the-missing-piece-to-the-react-testing-puzzle-c51cd30df7a0
The full documentation with all the assertions: http://bruderstein.github.io/unexpected-react
Note that in a mocha jsdom environment you also need a polyfill for requestAnimationFrame
* Assert React component's output using the shallow renderer
* Assert React component's output using the full renderer and JSX "expected" values (e.g. TestUtils.renderIntoDocument())
* Assert React component's output using the test renderer (react-test-renderer (require unexpected-react/test-renderer)
* Trigger events on components in shallow, full and test renderers
* Locate components using JSX queries in shallow, full and test renderers
* All assertions work identically with the shallow, full and test renderers, allowing you to mix and match in your tests, based on what you need.
* Checking a simple render
``js
var todoList = TestUtils.renderIntoDocument(
);
expect(
todoList,
'to have rendered',
* Direct rendering for shallow and deep rendering:
`js
expect(
,
'when rendered',
'to have rendered',
);
`
* Triggering an event on a button inside a subcomponent (using the
eventTarget prop to identify where the event should be triggered)`js
expect(
todoList,
'with event click',
'on', ,
'to contain',
Completed!
);
`
* Locating a component with
queried for then validating the render`js
expect(
todoList,
'queried for', ,
'to have rendered',
);
`
* Locating a component and then checking the state of the component with the full renderer
`js#async:true
expect(todoList,
'with event click',
'on', ,
'queried for',
).then(todoItem => {
// Here we're checking the state, but we could perform
// any operation on the instance of the component.
expect(todoItem.state, 'to satisfy', { completed: true });
});
`* Calling an event and validating the output using the test renderer
`js#evaluate:false
const unexpected = require('unexpected');
const React = require('react');
const TestRenderer = require('react-test-renderer');
const expect = unexpected.clone().use(require('unexpected-react/test-renderer'));describe('ClickCounterButton', function () {
it('shows the increased click count after a click event', function () {
const renderer = TestRenderer.create( );
expect(renderer,
'with event', 'click',
'to have rendered',
);
});
});
`Usage
`
npm install --save-dev unexpected unexpected-react
`Initialising
$3
`js#evaluate:falsevar unexpected = require('unexpected');
var unexpectedReact = require('unexpected-react');
var React = require('react');
var ReactTestUtils = require('react-dom/test-utils');
// Require the component we want to test
var MyComponent = require('../MyComponent');
// Declare our
expect instance to use unexpected-react
var expect = unexpected.clone()
.use(unexpectedReact);
describe('MyComponent', function () {
it('renders a button', function () {
var renderer = ReactTestUtils.createRenderer();
renderer.render( );
expect(renderer, 'to have rendered', );
});
});`$3
If you want to use the react-test-renderer, then
require('unexpected-react/test-renderer')`js#evaluate:falsevar unexpected = require('unexpected');
// Note that for the test-renderer, we need a different
require
var unexpectedReact = require('unexpected-react/test-renderer');var React = require('react');
var TestRenderer = require('react-test-renderer');
var MyComponent = require('../MyComponent');
// define our instance of the
expect function to use unexpected-react
const expect = unexpected.clone()
.use(unexpectedReact);
describe('MyComponent', function () {
it('renders a button', function () {
var renderer = TestRenderer.create( );
expect(renderer, 'to have rendered', );
});
});
`$3
If you want to assert over the whole virtual DOM, then you need to emulate the DOM
(note this library is not designed for use in the browser - it may be possible, but at the
very least, you'll need to disable the react-devtools)
If you don't need the virtual DOM, and you're just using the shallow renderer,
then the order of the requires is not important, and you obviously don't need the
emulateDom.js require.The order of
require's is important. unexpected-react must be required before react is required. That means unexpected-react must be required
before any other file is required that requires React (e.g. your components!)(You can also use the shallow renderer interchangeably with this setup)
`js#evaluate:false
// First require your DOM emulation file (see below)
require( '../testHelpers/emulateDom');var unexpected = require('unexpected');
// then require unexpected-react
var unexpectedReact = require('unexpected-react');
// then react
var React = require('react');
// ...and optionally the addons
var TestUtils = require('react-dom/test-utils');
// then our component(s)
var MyComponent = require('../MyComponent);
// define our instance of the
expect function to use unexpected-react
const expect = unexpected.clone()
.use(unexpectedReact);
describe('MyComponent', function () {
it('renders a button', function () {
var component = TestUtils.renderIntoDocument( ); // All custom components and DOM elements are included in the tree,
// so you can assert to whatever level you wish
expect(component, 'to have rendered',
);
});
});
`Using with Jest
unexpected-react works just the same with jest, complete with snapshot support (and you don't need your own DOM emulation, as jest has that built in). To use jest with the shallow and full renderers and include snapshot support, simply require unexpected-react/jest. Snapshotting the shallow renderer and the full DOM rendering works out of the box, no need to add any extra packages.e.g.
`js
const unexpectedReact = require('unexpected-react/jest');const expect = require('unexpected')
.clone()
.use(unexpectedReact);
`This
expect will then be used instead of the default one provided by jest.If you want to use the test renderer (the same as jest snapshots use), require
unexpected-react/test-renderer-jest.e.g.
`js
const unexpectedReact = require('unexpected-react/test-renderer-jest');const expect = require('unexpected')
.clone()
.use(unexpectedReact);
`Emulating the DOM
If you're using Jest, you can skip this part, as it comes with built in jsdom support.
For React v16, we recommend using jsdom-global and the requireAnimationFrame polyfill from Erik Möller, Paul Irish and Tino Zijdel. For previous versions, you can use the boilerplate presented here.
The
emulateDom file depends on whether you want to use domino, or jsdom. If you're using Jest, jsdom is built in, so you can ignore this section.For
jsdom:`js#evaluate:false
// emulateDom.js - jsdom variantif (typeof document === 'undefined') {
const jsdom = require('jsdom').jsdom;
global.document = jsdom('');
global.window = global.document.defaultView;
for (let key in global.window) {
if (!global[key]) {
global[key] = global.window[key];
}
}
}
`For
domino:`js#evaluate:false
// emulateDom.js - domino variantif (typeof document === 'undefined') {
const domino = require('domino');
global.window = domino.createWindow('');
global.document = global.window.document;
global.navigator = { userAgent: 'domino' };
for (let key in global.window) {
if (!global[key]) {
global[key] = global.window[key];
}
}
}
`React Compatibility
v5.x.x is compatible with React v16 and up
v4.x.x is compatible with React v15.5 and up
v3.x.x is compatible with React v0.14.x and v15. Warning with v15.5, but supported
v2.x.x is compatible with React v0.13.x and v0.14.xIt is not planned to make further releases of the v2 and v3 branch, but if you still need 0.13 / 0.14 support,
and are missing things from v4/5, please raise an issue.
Tests
For the shallow renderer, you can assert on the renderer itself (you can also write the same assertion for the result of
getRenderOutput())`js
var renderer = TestUtils.createRenderer();renderer.render( );
expect(renderer, 'to have rendered',
);
`If this fails for some reason, you get a nicely formatted error, with the differences highlighted:
`output
expected
to have rendered
`You can also use
when rendered to directly render a component to a shallow renderer:`jsexpect( ,
'when rendered',
'to have rendered',
);
`If you've emulated the DOM, you can write a similar test, but using
ReactDOM.render() (or TestUtils.renderIntoDocument())`js
var component = TestUtils.renderIntoDocument( )
expect(component, 'to have rendered',
);
``output
expected
to have rendered
`Note the major difference between the shallow renderer and the "normal" renderer, is that child components are also
rendered. That is easier to see with these example components:
`jsvar Text = React.createClass({
render() {
return {this.props.content};
}
});
var App = React.createClass({
render() {
return (
);
}
});`Rendering the
App component with the shallow renderer will not render the spans, only the
Text component with the props. If you wanted to test for the content of the span elements, you'd
need to use TestUtils.renderIntoDocument(...), or ReactDOM.render(...)Because unexpected-react
by default ignores wrapper elements, and also "extra" children (child``js
var component = TestUtils.renderIntoDocument(
// renders the Text components with the spans with the full renderer
expect(component, 'to have rendered',
hello
world
);
`
`js`
// renders the Text nodes with the full renderer'
expect(component, 'to have rendered',
);
`js
// renders the spans with the full renderer
expect(component, 'to have rendered',
`
The first test shows the full virtual DOM that gets rendered. The second test skips the "wrapper"
component, and leaves out the children of the components. The third tests skips both
the wrapper component, and the wrapper component.
Because stateless components can't be instantiated, renderIntoDocument won't return an instance back. when deeply rendered
Using the shallow renderer works as shown in the first example.
For full rendering, use the to render the component
`js`
expect(
'when deeply rendered',
'to have rendered',
Hello, Daniel!);
When using the normal renderer, unexpected-react makes use of react-render-hook,
which utilises the code from the React devtools. As there is no way for react-render-hook unexpectedReact.clearAll()
to know when a test is completed, it has to keep a reference to every rendered component. Whilst this shouldn't normally be an issue,
if you use a test runner that keeps the process alive (such as wallaby.js), it is a good idea to call in a global beforeEach() or afterEach() block. This clears the cache of rendered nodes.
* [DONE] ~~There are some performance optimisations to do. The performance suffers a bit due to the possible asynchronous nature of the inline assertions. Most of the time these will be synchronous, and hence we don't need to pay the price.~~
* [DONE] ~~queried for` implementation~~
* [DONE] ~~Directly calling events on both the shallow renderer, and the full virtual DOM renderer~~
* [DONE] ~~Support Snapshot testing in Jest~~
* Cleanup output - where there are no differences to highlight, we could skip the branch
We welcome pull requests, bug reports, and extra test cases. If you find something that doesn't work
as you believe it should, or where the output isn't as good as it could be, raise an issue!
Huge thanks to @Munter for unexpected-dom,
and along with @dmatteo from Podio for handing over the unexpected-react name.
Unexpected is a great library to work with, and I offer my sincere thanks to @sunesimonsen
and @papandreou, who have created an assertion library that makes testing JavaScript a joy.