Just like with utility modules, creating tests for React components starts with creating the __tests__
directory. Navigate to ~/snapterest/source/components/
and create the __tests__
directory.
The first React component that we'll test will be our Header
component. Create Header-test.js
in the ~/snapterest/source/components/__tests__
directory:
jest.dontMock('../Header.react'), describe('Header component', function () { it('renders provided header text', function () { var React = require('react'), var ReactDOM = require('react-dom'), var TestUtils = require('react-addons-test-utils'), var Header = require('../Header.react'), var header = TestUtils.renderIntoDocument( <Header text="Testing..." /> ); var actualHeaderText = ReactDOM.findDOMNode(header).textContent; expect(actualHeaderText).toBe('Testing...'), var defaultHeader = TestUtils.renderIntoDocument( <Header /> ); var actualDefaultHeaderText = ReactDOM.findDOMNode(defaultHeader).textContent; expect(actualDefaultHeaderText).toBe('Default header'), }); });
By now, you can recognize the structure of our test files. First, we tell Jest not to mock the Header
component. Then, we define our test suit, and we give it a name, 'Header component'
. Our test suit has one spec named, renders provided header text
. As the name suggests, it tests whether our Header
component renders the provided header text. The implementation of that spec has a number of new things that we'll discuss. Let's take a closer look at them.
First, we import React and ReactDOM:
var React = require('react'), var ReactDOM = require('react-dom'),
Then, we import one of the React add-ons:
var TestUtils = require('react-addons-test-utils'),
TestUtils
helps you test the React components with any test framework that you choose. Naturally, it works great with Jest. Let's install it:
npm install --save-dev react-addons-test-utils
Next, in order to test our Header
component, we need to import it:
var Header = require('../Header.react'),
Our next task is to render the Header
component to the DOM. The TestUtils
add-on has a helper renderIntoDocument()
function that does exactly this:
var header = TestUtils.renderIntoDocument( <Header text="Testing..." /> );
We pass the React <Header text="Testing..." />
component instance to the renderIntoDocument()
function as a parameter. Notice that in this case, our Header
component instance has a text
property. renderIntoDocument()
returns a reference to that component.
Now, we have a reference to our Header
component that is rendered to the DOM. Our next task is to check what header text did it render. This means that we need to perform the following steps:
Do you remember what method React provides us to find the component's DOM node? Did you say ReactDOM.findDOMNode()
?
ReactDOM.findDOMNode(header)
We pass header
to ReactDOM.findDOMNode()
as a parameter. As a result, ReactDOM.findDOMNode()
returns a DOM node element. Now we can access its textContent
property:
ReactDOM.findDOMNode(header).textContent;
The value of the textContent
property becomes the actual header text:
var actualHeaderText = ReactDOM.findDOMNode(header).textContent;
The final step is to create an expectation and match it with the expected text:
expect(actualHeaderText).toBe('Testing...'),
As you can tell, we're expecting Testing...
to be rendered as a DOM node text.
Great! Now we can test that the provided header text to the Header
component instance is rendered to the DOM.
What happens when we create the Header
component instance without providing any header text?
Let's find this out by rendering another instance of the Header
component to the DOM:
var defaultHeader = TestUtils.renderIntoDocument( <Header /> );
Only this time, it has no text
property. We'll call this component reference defaultHeader
. Let's find the defaultHeader
component's DOM node element and access its textContent
property:
var actualDefaultHeaderText = ReactDOM.findDOMNode(defaultHeader).textContent;
This will be our actual header text rendered by default by a Header
component. Finally, we create an expectation and match this with the expected text:
expect(actualDefaultHeaderText).toBe('Default header'),
In this case, actualDefaultHeaderText
must be equal to Default header
.
This is how you test what your React component renders. You might be wondering how do you test the behavior of your React component?
That's what we'll discuss next!
Create the Button-test.js
file in the ~/snapterest/source/components/__tests__/
directory:
jest.dontMock('../Button.react'), describe('Button component', function () { it('calls handler function on click', function () { var React = require('react'), var TestUtils = require('react-addons-test-utils'), var Button = require('../Button.react'), var handleClick = jest.genMockFunction(); var button = TestUtils.renderIntoDocument( <Button handleClick={handleClick} /> ); var buttonInstance = TestUtils.findRenderedDOMComponentWithTag(button, 'button'), TestUtils.Simulate.click(buttonInstance); expect(handleClick).toBeCalled(); var numberOfCallsMadeIntoMockFunction = handleClick.mock.calls.length; expect(numberOfCallsMadeIntoMockFunction).toBe(1); }); });
The Button-test.js
file will test our Button
component, and specifically, check whether it triggers the event handler function when you click on it. Without further ado, let's focus on the 'calls handler function on click'
spec implementation:
var React = require('react/addons'), var TestUtils = require('react-addons-test-utils'), var Button = require('../Button.react'), var handleClick = jest.genMockFunction(); var button = TestUtils.renderIntoDocument( <Button handleClick={handleClick} /> ); var buttonInstance = TestUtils.findRenderedDOMComponentWithTag(button, 'button'), TestUtils.Simulate.click(buttonInstance); expect(handleClick).toBeCalled(); var numberOfCallsMadeIntoMockFunction = handleClick.mock.calls.length; expect(numberOfCallsMadeIntoMockFunction).toBe(1);
First, we're importing React with the add-ons module and our Button
component. As usual, we tell Jest not to mock the Button
component.
Before we continue with implementing our spec, let's talk about how we're going to implement it. The plan is to:
Button
component instance with our mock function that imitates a click handler.Button
component.Let's generate a mock function:
var handleClick = jest.genMockFunction();
The jest.genMockFunction()
function returns the newly generated Jest mock function ; we name it handleClick
.
Next, we render the instance of our Button
component to the DOM:
var button = TestUtils.renderIntoDocument( <Button handleClick={handleClick} /> );
This Button
component instance receives our mock handleClick
function as a property.
Then, we find the Button
component instance rendered to the DOM:
var buttonInstance = TestUtils.findRenderedDOMComponentWithTag(button, 'button'),
The TestUtils.findRenderedDOMComponentWithTag()
method finds one instance of the Button
component that is rendered as a button
tag. We store that instance in the buttonInstance
variable.
Next, we simulate a click on that component instance:
TestUtils.Simulate.click(buttonInstance);
TestUtils.Simulate
simulates an event dispatch on a DOM node. For our purposes, we need to simulate a click event dispatch. For this, we need to call the TestUtils.Simulate.click()
method and pass buttonInstance
as a DOM node element. TestUtils.Simulate
provides an event method such as click()
for all events supported by React.
Finally, we create an expectation:
expect(handleClick).toBeCalled();
As you might have guessed, we expect our handleClick
mock function to be called at least once during our test.
What we also want to check is whether it's called exactly once. How do we check the number of calls made into our handleClick
mock function? All the Jest mock functions have a special .mock
property that stores all the data about how the mock function was called. We'll take a look at our handleClick
mock function's .mock
property to find out how many times handleClick
was called:
var numberOfCallsMadeIntoMockFunction = handleClick.mock.calls.length;
The .mock
property has the .calls
property, which is an array, that represents all the calls that have been made into our handleClick
mock function. The length of that array will be the number of calls made into handleClick
. We store this number in the numberOfCallsMadeIntoMockFunction
variable and then create another expectation:
expect(numberOfCallsMadeIntoMockFunction).toBe(1);
We expect the number of calls made into our mock function to be exactly 1.
Right now, we've finished creating our tests. However, we can't run them yet; can you guess why? Let's think about it. We're testing the React components written in the JSX syntax, but Jest doesn't understand the JSX syntax. So, if you run npm test
now, both the component tests will fail and report SyntaxError
.
What we need to do is to configure Jest in order to use a preprocessor provided by the babel-jest
module. Add the jest
property to your ~/snapterest/package.json
file:
"jest": { "scriptPreprocessor": "<rootDir>/node_modules/babel-jest", "testFileExtensions": ["es6", "js"], "unmockedModulePathPatterns": [ "<rootDir>/node_modules/react" ] }
Install babel-jest
:
npm install --save-dev babel-jest
If you're curious about what unmockedModulePathPatterns
does, then go to https://facebook.github.io/jest/docs/api.html#config-unmockedmodulepathpatterns-array-string.
Now it's time to run all our tests.
Navigate to ~/snapterest/
and run this command:
npm test
All your test suits should PASS
:
PASS source/utils/__tests__/TweetUtils-test.js (0.134s) PASS source/utils/__tests__/CollectionUtils-test.js (0.146s) PASS source/components/__tests__/Button-test.js (2.802s) PASS source/components/__tests__/Header-test.js (2.877s) 4 tests passed (4 total) Run time: 4.728s
Log messages, such as these, will help you sleep well at night and go on holidays, without the need to constantly check your work e-mails.
18.117.234.225