Jest Create React Tests

Elvenware Logo

TOC

Test button click

It's time to write some unit tests with Jest.

Call the enzyme simulate method to simulate clicking the button. Check to see if the form now contains the value we expect it to contain. In other words, check that the button click method worked.

it('renders button click message', () => {
   const wrapper = shallow(<App />);
   const nineSign = <p className="App-intro">File: url-file.js</p>;
   wrapper.find('button.elf').simulate('click');
   expect(wrapper.contains(nineSign)).toEqual(true);
});

To run only a single test use it.only or fit:

fit('renders button click message', () => {
   const wrapper = shallow(<App />);
   etc...   
})

With only:

it.only('renders button click message', () => {
   const wrapper = shallow(<App />);
   etc...
})

NOTE: When I write something like etc... or and so on... or You write the code, then I'm saying that I expect you to complete the code as an exercise. I usually cut and paste working code into a document like this, and then delete the parts I want readers to complete. I usually mark the missing code as described above.

Running Tests

Open up src/App.test.js, shown below. This is a test that allows you to see if the syntax in App.js is good enough to allow the component to be loaded.

To run the tests, type: npm test in the project's root directory.

Jest tests are in files with these extensions:

Here is the simple default test generated by create-react-app:

import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';

it('renders without crashing', () => {
   const div = document.createElement('div');
   ReactDOM.render(<App />, div);
   ReactDOM.unmountComponentAtNode(div);
});

This test is built with Jest library, but the syntax looks just like the very popular Jasmine library. In most cases, it also behaves much like Jasmine.

Writing Tests

Let's update the default example by wrapping the test in a describe method:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

describe('Jest Create React Tests', function () {

   it('renders without crashing', () => {
       const div = document.createElement('div');
       ReactDOM.render(<App />, div);
       ReactDOM.unmountComponentAtNode(div);
   });

});

Enzyme

We need a tool to capture and parse the output created by our React components. We test that output to see if it is valid. The tool that helps us do this is airbnb's Enzyme.

If you have not done so already, install Enzyme with these commands:

npm install --save-dev enzyme react-test-renderer enzyme-adapter-react-16

Enzyme Test of Component Output

The updated tests shown below import shallow from enzyme. It grabs text from our component and then checks to see if the text is what we expect it to be.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import Adapter from 'enzyme-adapter-react-16';
import { configure, shallow } from 'enzyme';
configure({ adapter: new Adapter() });

describe('jest test', function() {

   it('renders without crashing', () => {
       const div = document.createElement('div');
       ReactDOM.render(<App />, div);
       ReactDOM.unmountComponentAtNode(div);
   });

   it('renders and reads H1 text', () => {
       const wrapper = shallow(<App />);
       const welcome = <h1 className="App-title">Welcome to React</h1>;
       expect(wrapper.contains(welcome)).toEqual(true);
   });

});

Perhaps you are getting an error when you run these tests. You may be able to fix it by yourself, but let's learn how to use ElfDebugEnzyme to help you find the error.

NOTE: create-react-app has recently changed the default code they put in App.js. Rather than trying to keep up with their changes, let's just check to make the h1 tag "Welcome to React" is in their render method. That might look a bit like this:

<header className="App-header">
  <h1 className="App-title">Welcome to React</h1>
  <img src={logo} className="App-logo" alt="logo" />
  <p>
    Edit <code>src/App.js</code> and save to reload.
  </p>
  <a
    className="App-link"
    href="https://reactjs.org"
    target="_blank"
    rel="noopener noreferrer"
  >
    Learn React
  </a>
</header>

What we are looking for is the H1 tag near the top of the JSX quoted above. Be sure you don't get tripped up over H1 vs H2 tag differences.

Test Output

I'm expecting the test output to look something like this:

PASS  src/App.test.js
 jest test
   ✓ renders without crashing (4ms)
   ✓ renders and reads H1 text (3ms)
   ✓ renders button click message (1ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.364s, estimated 1s
Ran all test suites.

 console.log ElfDebugEnzyme.js:45
   App.test.js:
   <h1 className="App-title">
     Welcome to React
   </h1>

 console.log src/App.js:14
   state

 console.log ElfDebugEnzyme.js:45
   App.test.js:
   App.test.js:
   <p className="App-intro">
     File:
     'url-file.js'
   </p>

Elf Debug Enzyme

Get a copy of my gist ElfDebugEnzyme and save it to a file with the same name (ElfDebugEnzyme.js) in the src directory:

NOTE: The file is available via get-gist.

One way to save the file is to the choose the Raw button on GitHub, and then blockcopy the code. Now create a new file in WebStorm and paste in the code.

Now import ElfDebugEnzyme into your test:

import ElfDebugEnzyme from './ElfDebugEnzyme';

const elfDebugEnzyme = new ElfDebugEnzyme(true, 'App.test.js', true);

Use ElfDebugEnzyme to display the first H1 element:

it('renders and reads H1 text', () => {
    const wrapper = shallow(<App />);
    const welcome = <h1 className="App-title">Welcome to React</h1>;
    elfDebugEnzyme.getFirst(wrapper, welcome.type, true);
    expect(wrapper.contains(welcome)).toEqual(true);
});

The key line is this one:

elfDebugEnzyme.getFirst(wrapper, welcome.type, true);

First, note that in this case welcome.type will be H1 because that is the type we are searching on. Thus the two following lines are equivalent:

elfDebugEnzyme.getFirst(wrapper, welcome.type, true);
elfDebugEnzyme.getFirst(wrapper, 'h1', true);

But we prefer the first because it is DRYer. (DRY: Don't Repeat Yourself.) If you decide to search on an H2 instead of an H1, you need make the change in only one place.

The elfDebugEnzyme call is to its getFirst method. The code asks to see the contents of the first H1 element generated by your React component. In our case, there is only one H1 element, and it looks like this, when printed out by ElfDebugEnzyme:

console.log src/ElfDebugEnzyme.js:66
  Caller: App.test.js:
  Debug value:
  <h1 className="App-title">
      Welcome to React
  </h1>

Lets review the use of find.type. When using ElfDebugEnzyme we don't need to hard code in the element we are searching on. Instead, use type.

Here is a second example included to show you why type is helpful. This first code sample is not as good as it could be because we hard code in the button type when calling the second parameter of getFirst:

const find = <button>Test Foo Route</button>;        
elfDebugEnzyme.getFirst(wrapper, 'button');

This is better because find.type is automatically set to button:

const find = <button>Test Foo Route</button>;        
elfDebugEnzyme.getFirst(wrapper, find.type);

The big advantage is that find.type automatically picks up on changes to find. If you change button to p (paragraph) there would be no need to change the call to Enzyme:

const find = <p>Test Foo Route</p>;
elfDebugEnzyme.getFirst(wrapper, find.type);

If you pass in false to the last parameter of the constructor, then you get this output:

console.log src/ElfDebugEnzyme.js:66
  Caller: App.test.js:
  Debug value:
  {
      "type": "h1",
      "key": null,
      "ref": null,
      "props": {
          "className": "App-title",
          "children": "Welcome to React"
      },
      "_owner": null,
      "_store": {}
  }

This output might be more useful in some cases.

How ElfDebugEnzume Works

The relevant code in ElfDebugEnzyme is simple enough. It asks enzyme to find the first matching element and display the debug information associated with that element. In this case we might have passed in H1 or P as the element. If we passed in H1, then it will find the first H1 tag in our component. If we pass in P, it will find the first paragraph tag:

wrapper.find(element).first().debug();

Once you know the HTML that is being generated, you can adjust your test to ensure that it properly matches the HTML. Or vice versa, depending on your needs and circumstances.

Watch the ElfDebugEnzyme video

Watch a second video documenting additional updates.

Okay, enough about ElfDebugEnzyme. Let's go back to writing more tests.

Hint

Don't nest folders! There should be only one folder called Week02-ReactJest in your repository. It should be in the root of your repository.

The warnings

You are probably no longer getting these warnings, but just in case I will include a few notes about warnings I received.

Warning: ReactTestUtils has been moved to react-dom/test-utils. Update references to remove this warning. Warning: Shallow renderer has been moved to react-test-renderer/shallow. Update references to remove this warning

For awhile, I was seeing warnings due to outdated react-addons-test-utils and enzyme modules. I believe Enzyme has solved these issues. If you see the warnings below, try updating these two packages:

npm uninstall react-addons-test-utils enzyme
npm install react-addons-test-utils enzyme

Setup UI

Ignore me. This section does not apply to this assignment.

Shrink and change the color of the header by making changes like these in App.css:

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 10vmin;
  pointer-events: none;
}

.App-header {
  background-color: green;
  min-height: 10vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

You should change the logo. You can use whatever image you want. Working in my project root, I choose to copy in my tree of life from Address Simple like this:

cp ../week05-address-simple/source/images/tree-of-life.svg src/logo.svg

You don't need to modify webpack or do anything else to teach your program to load a PNG or SVG. Our create-react-app already knows how to do this.