The Basics of Test Driven Development with Jest and Enzyme

Cathy D'Onofrio
7 min readMar 6, 2021
Photo by Jantine Doornbos on Unsplash

Recently, I started learning Test Driven Development, (TDD). True to its name, this means tests are written before any functional code is written. At most, a shell version of the code is written so that the tests can call it, but the code should not be functional until the test is written.

Tests should always fail before any real code is written. You should be suspicious if they are not! Otherwise, you can’t be sure that your tests are actually catching any errors.

Why TDD?

TDD is automated and more efficient than manual testing. If tests are written upfront, code tends to be better planned, easier to test and make changes, have fewer bugs, and have better code coverage.

Another note about writing tests upfront: tests should be written based on behavior, not implementation. This will prevent having to rewrite tests if the code changes.

Types of Tests

There are three main types of tests. We’ll only work through an example of a unit test.

Unit testing: This tests one piece of code, usually a function. These tests are more diagnosable since they are so targeted. However, that means they can also be brittle since they can only test that certain functions are working the way we want them to. It doesn’t mean our app as a whole is working.

Integration testing: This tests how multiple units work together. It tends to be more robust than unit tests since it tests more broadly. However, it can be more difficult to diagnose failure.

Acceptance: End to End (E2E) testing is how a user actually interacts with the app from user interface to database. This usually includes something like Selenium, which won’t be covered in this post.

Because unit testing and integration have their own strengths and compensate for each other’s weaknesses, it’s up to the programmer and their team to assess the proper balance between the two.

Jest and Enzyme

I’ve begun my testing journey using Jest and Enzyme in React. This post assumes you have some basic knowledge of React and have an app set up with create-react-app. This won’t cover Redux or Hooks.

Jest is a JavaScript testing framework that is included in create-react-app, although its uses are not limited to React. Enzyme (created by Airbnb) is a library that makes it easier to test React components specifically. It isn’t included in create-react-app, but it’s very common to use with Jest to make testing easier.

Shallow vs Mount

Enzyme creates a virtual DOM and allows for testing without a browser. Through Enzyme, we can search through the DOM with jQuery-style selectors. There are two ways to render. The first way is shallow rendering, which goes one level deep into the code, without worrying about the behavior of the component’s children. The second way to render is with mount, which goes a level deeper and tests a components children. There are pros and cons to both, which have been argued at length here and here. Since this post is covering the basics, we’ll stay in the shallow end.

Creating a Basic Test

Finally! It’s time to create a basic test. For this test, all we’re going to do now is make sure our <App /> component is rendered.

As mentioned, Jest is already included in create-react-app. You can install Enzyme separately by running the command below.

npm install --save-dev enzyme jest-enzyme enzyme-adapter-react-16

**Note: at the time of this post, React has updated to React 17, but Enzyme Adapter 17 is not available. For the most part, Enzyme Adapter 16 will work. I believe the existing issues are with mount, which won’t be covered here. However, there is an unofficial Enzyme Adapter 17 here if needed.

Your main App component should look something like this. It’s okay if you have some modified content in the div, but leave any child components out for now to keep things simple.

Your App.test.js file should look like this by default, but we are going to change a lot very soon!

Since we are using Enzyme, we can remove the import from @testing-libary/react. We can instead add import Enzyme, { shallow } from enzyme; to configure Enzyme to use the adapter, and { shallow } to create the shallow rendering we discussed earlier. Add import EnzymeAdapter from ‘enzyme-adapter-react-16’ to create the adapter. Note, this import may vary if you decide to use the unofficial 2017 adapter and you should refer to that documentation.

Completely remove the test() and all of its contents so we can go through the steps of writing a new one step-by-step.

To set up a new instance of an Enzyme React adapter, add Enzyme.configure({ adapter: new enzymeAdapter() }); below the imports.

Before we write a test, you should run npm test, a script that comes with create-react-app. You should get this error:

FAIL  src/App.test.js
● Test suite failed to run
Your test suite must contain at least one test.at onResult (node_modules/@jest/core/build/TestScheduler.js:175:18)
at node_modules/@jest/core/build/TestScheduler.js:304:17
at node_modules/emittery/index.js:260:13
at Array.map (<anonymous>)
at Emittery.Typed.emit (node_modules/emittery/index.js:258:23)
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 6.499 s
Ran all test suites related to changed files.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press q to quit watch mode.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press Enter to trigger a test run.

If you got this error, great! Your test suite is set up properly. Note that you can use any of the commands under Watch Usage.

Now, let’s write a test! Let’s test that the App component renders without error. To do this, write a new empty test shell. In the test, we want to do a shallow rendering of our component, App. We put the component we are testing in the shallow wrapper we imported from the Enzyme library. By convention, we call this wrapper.

test("renders without error", () => { 
const wrapper = shallow(<App/>);
});

If you run npm test here, your test should be passing. However, since this is test driven development, we want our tests to fail before they can pass. To solve this, add a data-test attribute to the top level of the component we are rendering. In this case, that is the div in <App />. Note, you don’t need to use data-test for it to work, but it is the convention. Leave it empty for now, so we can find the attribute in the test and let it fail.

Now, we can add more conditions to our test. Navigate back to App.test.js, where we will use find(selector) on the wrapper to find the data-test value we plan to add in the shallow component. Let’s assume that the data-test value will be component-app. Note, the attribute syntax for the selector is further detailed in the documentation.

test("renders without error", () => { 
const wrapper = shallow(<App/>);
const appComponent = wrapper.find("[data-test='component-app']")
});

Great! So far, we can identify what component we are rendering, and find an attribute based on its value. If you run npm test, however, you’ll see that the test is still passing. This is because all we did so far was define two variables. We haven’t made an assertion statement, which is what you use to actually test something. You need an assertion statement to have the ability to throw an error, otherwise your test will never fail.

To do this, we can use an expect assertion from Jest. In this particular case, we can check to make sure that the returned length is 1, to ensure that we are receiving just node back. A popular method to use is .toBe(value), which you can read about more in the docs.

expect(appComponent.length).toBe(1);

The full App.test.js file should look like this:

Run npm test if it’s not already running, and hopefully you will get your first failure:

FAIL  src/App.test.js (5.451 s)
✕ renders without error (11 ms)
● renders without errorexpect(received).toBe(expected) // Object.is equalityExpected: 1
Received: 0
18 | const appComponent = findByTestAttr(wrapper,"component-app")
19 | // //need this test to fail first
> 20 | expect(appComponent.length).toBe(1)
| ^
21 |
22 | });
23 |
at Object.<anonymous> (src/App.test.js:20:31)Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 7.379 s
Ran all test suites.

Perfect! The important part of the error clearly states that we expected one node to have the data-test attribute with the value of component-app. Our <App /> component doesn’t have that, so the test is failing.

To get this test to pass, you can navigate back to App.js and add the value in. Your file should look like this.

With npm test running, you should get this result.

PASS  src/App.test.js
✓ renders without error (8 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.578 s, estimated 6 s
Ran all test suites.

I hope this post was helpful in getting started with test-driven development! This is of course a very basic test, but this should help you understand why writing good tests is so important.

Sources: https://blog.logrocket.com/jest-and-enzyme-unit-testing-in-react-in-2021/

https://www.udemy.com/course/react-testing-with-jest-and-enzyme/

--

--