React Unit is a lightweight unit test library for ReactJS with very few (js-only) dependencies.
By using react-unit
you can run your ReactJS unit tests directly from node or gulp without having to install any heavyweight external dependencies (such as jsdom
, phantomjs
, the python runtime, etc.).
Note: for testing simple components you might also be interested in react-cucumber.
npm install --save-dev react-unit
and then, in your tests:
var React = require('react');
var createComponent = require('react-unit');
describe('MyComponent', () => {
it('should echo the value', () => {
var component = createComponent(<MyComponent value="hello, world!" />);
var input = component.findByQuery('input')[0];
expect(input.props.value).toBe('hello, world!');
});
it('should trigger events', () => {
var changedValue;
function onChange(e) { changedValue = e.target.value; }
var component = createComponent(<MyComponent onChange={onChange} />);
var input = component.findByQuery('input')[0];
input.onChange({target:{value: 'hi, everyone!'}});
expect(changedValue).toBe('hi, everyone!');
});
});
Note that, while this example is using Jasmine, react-unit
should work with any other test language of your choice.
To use react-unit
just require the createComponent
function:
var createComponent = require('react-unit');
Then use it to create your component:
var component = createComponent(<MyComponent value="hello, world!" />);
or (if, for some reason you are not into JSX):
var component = createComponent(React.createElement(MyComponent, { value: "hello, world!" }));
Now that you have a representation of your component you can use it to find actual HTML elements calling findByQuery
:
var allInputs = component.findByQuery('input');
var allRows = component.findByQuery('.row');
var allFirstNames = component.findByQuery('[name=firstName]');
By now you probably noted that findByQuery
takes something suspiciously similar to jQuery selectors. This is not an innocent coincidence, react-unit
is bundled with the amazing jQuery Sizzle to allow you to search your react DOM using query selectors.
In addition to findByQuery
you can use findBy
to test every element using a custom function:
var all = component.findBy(function() { return true; }); // () => true
var moreThanTwo = component.findBy(function(c) { return c.props.value > 2 });
To find elements by their ref
attribute, you can use the findByRef
method:
var allMyRefs = component.findByRef('myRef');
If you want to find a component using a component variable instead of a string expression, you can use findByComponent
:
var component = createComponent.shallow(<CompositeComponent />); // Note: the .shallow!
// or var component = createComponent.interleaved(<CompositeComponent />);
var children = component.findByComponent(ChildComponent);
Note that findByComponent
only works with shallow
and interleaved
rendering modes. See Rendering Modes below for more details.
If you want to test event handling, you can bind a handler to your component:
var changeEvent;
function handler(e) { changeEvent = e; }
var component = createComponent(<MyComponent onChange={handler} />);
Then find and interact with any element in the component:
component.findByQuery('some selector')[0].onChange('some event');
Finally assert the event:
expect(changeEvent).toBe('some event');
If at any point you want to inspect the rendered component you can use:
console.log(component.dump());
// createComponent :: ReactElement -> Component
createComponent = (reactElement) => Component
Renders reactElement
using the deep rendering strategy (see Rendering Modes for more details). Returns the rendered Component
.
This method produces a component tree that is somewhat similar to applying ReactDOM.render
.
For example:
var createComponent = require('react-unit');
var component = createComponent(<MyComponent />);
More examples in test/create-component.jsx.
// createComponent.shallow :: ReactElement -> Component
createComponent.shallow = (reactElement) => Component
Renders reactElement
using the shallow rendering strategy (see Rendering Modes for more details). Returns the rendered Component
.
This method produces a shallow component tree. That is, it renders the root component and all the children HTML nodes, stopping at the first child component level.
For example:
var createComponent = require('react-unit');
var component = createComponent.shallow(<MyComponent />);
More examples in test/create-component-shallow.jsx.
// createComponent.interleaved :: ReactElement -> Component
createComponent.interleaved = (reactElement) => Component
Renders reactElement
using the interleaved rendering strategy (see Rendering Modes for more details). Returns the rendered Component
.
This method produces a component tree that interleaves react components and actual rendered components.
For example:
var createComponent = require('react-unit');
var component = createComponent.interleaved(<MyComponent />);
More examples in test/create-component-interleaved.jsx.
// findByQuery :: String -> [Component]
component.findByQuery => (sizzleExpression) => [Components]
Returns all the descendant elements of component
matching sizzleExpression
.
For example:
var inputs = component.findByQuery('input');
More examples in test/find-by-query.jsx.
// findByComponent :: ReactElement -> [Component]
component.findByComponent => (reactElement) => [Components]
Returns all the descendant elements of component
of type reactElement
. Note that findByComponent
only works with shallow
and interleaved
rendering modes. See Rendering Modes below for more details.
For example:
// assuming: var MyItem = React.createClass({ ... });
var items = component.findByComponent(MyItem);
More examples in test/find-by-component.jsx.
// findBy :: (Component -> bool) -> [Component]
component.findBy => (fn) => [Components]
Returns all the descendant elements of component
for whom fn
returns true
.
For example:
var moreThanTwos = component.findBy(c => c.props.value > 2);
More examples in test/find-by.jsx.
// findByRef :: String -> [Component]
component.findBy => (ref) => [Components]
Returns all the descendant elements of component
matching the ref
attribute.
For example:
var allMyRefs = component.findByRef('myRef');
More examples in test/find-by-ref.jsx.
// dump :: () -> String
component.dump => () => String
Returns a string representation of the pseudo-HTML of the component. This method is very useful for troubleshooting broken tests.
For example:
var html = component.dump();
// or
console.log(component.dump());
component.texts // :: [String]
component.text // :: String
Return the text of all the descendant elements of component
. texts
is a flat array containing the texts of every descendant element in depth order. text
behaves like DOMNode.textContent
(i.e. component.texts.join('')
).
Some examples in test/text.jsx.
// key :: Object
component.props
The props
object of component
.
// key :: String
component.key
The key
of component
.
// ref :: String
component.ref
The ref
of component
.
By default react-unit
will use a deep (recursive) rendering strategy. This produces an output that is very similar to that of ReactDOM.render
.
For example, given:
var Person = React.createClass({
render: function() {
var children = React.Children.map(this.props.children, (c,i) => <li key={i}>{c}</li>);
return <div><h1>{this.props.name}</h1><ul>{children}</ul></div>
}
});
Calling createComponent
in a composite component:
var component = createComponent(
<Person name="Homer">
<Person name="Bart"/>
<Person name="Lisa" />
<Person name="Maggie" />
</Person>);
Results in a representation of the following HTML:
<div>
<h1>Homer</h1>
<ul>
<li>
<div><h1>Bart</h1><ul></ul></div>
</li>
<li>
<div><h1>Lisa</h1><ul></ul></div>
</li>
<li>
<div><h1>Maggie</h1><ul></ul></div>
</li>
</ul>
</div>
In other words, the output is the HTML that results of calling the render method of every component. Note that, as a side-effect of deep rendering, component tags (e.g. <Person/>
) were erased from the HTML representation.
In the example above you find Lisa
with:
var lisa = component.findByQuery('div > ul > li > div > h1')[1];
On the flip side, you cannot use findByQuery
to find your components because, after rendering, they were replaced by the HTML they generate in their render
method:
var persons = component.findByQuery('Person');
expect(persons.length).toEqual(0);
Sometimes you might want to stop rendering after the first level of components. In true unit test spirit you would like to just test a component assuming the components it depends upon are working.
To achieve this you can use createComponent.shallow
as follows:
var component = createComponent.shallow(
<Person name="Homer">
<Person name="Bart"/>
<Person name="Lisa" />
<Person name="Maggie" />
</Person>);
And the result would be a representation of the following pseudo-HTML:
<div>
<h1>Homer</h1>
<ul>
<li>
<Person name="Bart"/>
</li>
<li>
<Person name="Lisa"/>
</li>
<li>
<Person name="Maggie"/>
</li>
</ul>
</div>
To find Lisa
you could use any of the following:
var lisaByAttr = component.findByQuery('Person[name=Lisa]')[0];
var lisaByTagAndOrder = component.findByQuery('Person')[1];
var lisaByCompAndOrder = component.findByComponent(Person)[1];
And access the properties as usual:
expect(lisaByAttr.prop('name')).toEqual('Lisa');
This rendering mode is similar to the deep mode above with the exception that components are NOT erased form the HTML representation. This means that you can mix and match HTML tags and react components in your findByQuery
selectors.
To use interleaved rendering call createComponent.interleaved
as follows:
var component = createComponent.interleaved(
<Person name="Homer">
<Person name="Bart"/>
<Person name="Lisa" />
<Person name="Maggie" />
</Person>);
The result would be a representation of the following pseudo-HTML:
<Person name="Homer">
<div>
<h1>Homer</h1>
<ul>
<li>
<Person name="Bart">
<div><h1>Bart</h1><ul></ul></div>
</Person>
</li>
<li>
<Person name="Lisa">
<div><h1>Lisa</h1><ul></ul></div>
</Person>
</li>
<li>
<Person name="Maggie">
<div><h1>Maggie</h1><ul></ul></div>
</Person>
</li>
</ul>
</div>
</Person>
And you can find components with:
var lisaComp = component.findByQuery('Person[name=Lisa]')[0];
var lisaCompAlt = component.findByComponent(Person)[2];
var lisaName = component.findByQuery('Person[name=Lisa] h1')[0];
var lisaNameAlt = lisaComp.findByQuery('h1')[0];
Using exclude
you can now leave a component out of test as if it didn't exist.
//single component
createComponent.exclude(ChildComponent)(ParentComponent);
createComponent.exclude(ChildComponent).shallow(ParentComponent);
createComponent.exclude(ChildComponent).interleaved(ParentComponent);
//multi components
createComponent.exclude([ChildComponent1, ChildComponent2])(ParentComponent);
createComponent.exclude([ChildComponent1, ChildComponent2]).shallow(ParentComponent);
createComponent.exclude([ChildComponent1, ChildComponent2]).interleaved(ParentComponent);
Using mock
you can now replace a component with another
//single mock
createComponent.mock(Actual, Mock)(ParentComponent);
//multi mock
createComponent
.mock(Actual1, Mock1)
.mock(Actual2, Mock2)(ParentComponent);
If you want to test components that use React's implicit context you can pass the context to the rendered components by calling withContext
:
createComponent.withContext({ key: value})(<MyComponentWithContext />);
Note that testing stateful components require additional effort. See test/stateful.jsx for more details.
For more examples on how to test events refer to test/events.jsx.
For more examples on finding elements by query selectors refer to test/find-by-query.jsx.
For more examples on finding element using a custom function refer to test/find-by.jsx.