It is commonly accepted among developers, following Michael Feathers’ definition, that code without tests can be categorized as legacy code. To prevent accumulating such legacy code, the test-driven development (TDD) approach offers a robust solution.
Although JavaScript and React.js have numerous tools for unit testing, this article will demonstrate how to build a basic React.js component using Jest and Enzyme, employing a TDD methodology.
The Advantages of TDD for React.js Component Creation
TDD offers numerous advantages for your codebase. High test coverage, a direct result of TDD, simplifies code refactoring while maintaining code clarity and functionality.
Creating React.js components often leads to rapid code expansion, becoming intricate due to state changes and service calls.
Components without unit tests become difficult-to-maintain legacy code. While adding tests after production code is possible, it risks overlooking testable scenarios. Creating tests first increases the likelihood of covering every logic branch in a component, simplifying refactoring and maintenance.
Strategies for Unit Testing React.js Components
Several strategies prove effective for testing a React.js component:
- Verifying that a specific
propsfunction is called when a particular event is dispatched. - Comparing the
renderfunction’s output, based on the current component state, to a predefined layout. - Checking if the number of child components aligns with an expected value.
To implement these strategies, we will utilize two tools renowned for their effectiveness in React.js testing: Jest and Enzyme.
Jest: A Powerful Tool for Unit Test Creation
Jest, an open-source test framework developed by Facebook, boasts excellent integration with React.js. It offers a command-line test execution tool similar to Jasmine and Mocha, simplifies mock function creation with minimal configuration, and provides a clear set of matchers for readable assertions.
Furthermore, Jest’s “snapshot testing” functionality proves invaluable for verifying component rendering results. We’ll use snapshot testing to capture and save a component’s tree structure. This saved snapshot can then be compared against subsequent rendering trees or any structure passed as the first argument to the expect function.
Enzyme: Facilitating React.js Component Mounting and Traversal
Enzyme provides a mechanism for mounting and traversing React.js component trees. This access allows us to interact with component properties, state, and children’s props for assertion purposes.
Enzyme offers two primary functions for component mounting: shallow and mount. While shallow loads only the root component into memory, mount constructs the complete DOM tree.
We will combine Enzyme and Jest to mount React.js components and execute assertions.

Setting Up the Development Environment
For this example, refer to this repo for the basic configuration.
We’ll be using these versions:
| |
Building a React.js Component Using TDD
Our first step is to create a failing test that attempts to render a React.js Component using Enzyme’s shallow function.
| |
Running this test results in the following error:
| |
Next, we create the component with the minimal syntax necessary to make the test pass.
| |
Moving on, we’ll ensure our component renders a predefined UI layout using Jest’s toMatchSnapshot function.
This method generates a snapshot file named [testFileName].snap in the __snapshots__ folder. This file represents the expected UI layout resulting from our component rendering.
However, adhering to pure TDD principles requires creating this snapshot file before calling the toMatchSnapshot function, intentionally causing the test to fail.
This might seem counterintuitive since we don’t initially know the format Jest uses for this layout.
It’s tempting to execute toMatchSnapshot first to observe the generated snapshot file, and that’s certainly an option. However, for true pure TDD, we must understand the structure of snapshot files.
The snapshot file mirrors the test’s name in its layout. For example, a test structured like this:
| |
Should have a corresponding entry in the exports section: Component A should do something 1.
For a more detailed understanding of snapshot testing, refer to here.
Therefore, we begin by creating the MyComponent.test.js.snap file.
| |
Next, we create the unit test to verify that the snapshot matches the component’s child elements.
| |
We can consider components.getElements as the output of the render method.
These elements are passed to the expect method for verification against the snapshot file.
Upon test execution, we encounter the following error:
| |
Jest indicates that the output from component.getElements does not correspond to the snapshot. We address this by incorporating the input element into MyComponent.
| |
Now, we enhance the input functionality by executing a function when its value changes. This is accomplished by defining a function within the onChange prop.
First, we modify the snapshot to deliberately fail the test.
| |
A consequence of altering the snapshot first is the importance of prop (or attribute) order.
Jest will alphabetically sort props received by the expect function before comparing against the snapshot. Therefore, we must maintain this order.
Executing the test now yields the following error:
| |
To pass this test, we simply provide an empty function to onChange.
| |
Next, we ensure the component’s state updates after the onChange event is dispatched.
This involves creating a new unit test that calls the onChange function in the input, passing an event to simulate a real UI event. Then, we verify that the component state contains a key named input.
| |
We are now greeted with this error:
| |
This signifies that the component lacks a state property named input.
We rectify this by introducing this entry into the component’s state.
| |
Our next task is to ensure that a value is assigned to this new state entry, obtained from the event.
Let’s create a test to confirm the state contains this value.
| |
Finally, we achieve a passing test by retrieving the value from the event and assigning it as the input value.
| |
With all tests passing, we can now refactor our code.
Let’s extract the function provided to the onChange prop into a new function called updateState.
| |
And there we have it - a simple React.js component constructed using TDD.
Summary
This example demonstrated pure TDD by meticulously following each step, writing minimal code to intentionally fail and subsequently pass tests.
While some steps may appear superfluous, tempting us to bypass them, doing so compromises the purity of our TDD approach.
While less strict TDD processes are valid and can work effectively, my recommendation is to diligently follow each step. Don’t be discouraged if it proves challenging - TDD is a skill that requires dedication to master, but the rewards are well worth the effort.
For those interested in delving deeper into TDD and the related behavior-driven development (BDD), I highly recommend reading “Your Boss Won’t Appreciate TDD” by fellow Toptaler Ryan Wilcox.