Writing unit-tests for your React components are important, but it also important that tests are robust. Brittle tests are not maintainable & cause frustration to developers.
This post addresses once such scenario that can cause brittle unit-tests, which is finding the nodes or DOM elements. Let’s work on making them robust.
DOM elements are identified using selectors such as id or CSS class-names. If the CSS class-names are generated dynamically then using them is out of the question. How using them as your identifier makes your test brittle? Well, if your code is refactored which changes the id of your DOM element or CSS class-name is changed, in such scenario, despite your component having the same functionality, the unit-test(s) will fail. E.g. We were using a
<button> element to create a button & id was
btnCreate but later on we decided to refactor it & use
<a> anchor tag & we refactored id to
To make your unit-tests immune to such changes, we use an identifier that is consistent through out the life-cycle of an application. For achieving the robustness we can use data-test attributes for the nodes or DOM Elements we want to identify within the test. An extra attribute we will added to the nodes or DOM Elements we want to find or raise assertions with help of unit testing framework & test utilities in the context & this selector will remain unchanged even if the underlying DOM element is changed which was used to represent the data received or will not be affected by code refactoring (unless the whole element is removed in which case test should fail).
How do we achieve this? Source code is available under a GitHub repository to download here. Let’s discuss about it.
To protect the unit-tests from the changes of
id or CSS
class-name we will introduce a
data-test-id attribute to the components or elements we need to find via unit tests. Refer to the
Workout.js component in the code example:
I’ve added the data-test-id attribute to 4 elements we want to identify for
Workout.js component via unit-test. We want to make sure when details about a workout are provided, the container is rendered along with name, target & group. For the example application, we’re using Jest & Enzyme to write this unit test. When the test starts, we setup the component using
shallow method of enzyme to render the component. Enzyme returns a
ShalloWrapper for the component & within this wrapper we will search & identify the components/elements we’re looking for.
A small utility method is written to search for nodes with data-test-id in shallow rendered component so we don’t duplicate logic across out tests. It is available under test/utils/testUtils.js
Now we will write the unit-test using Jest & Enzyme along with the help of above utility method. This is test is now immune from changes of id or CSS class-names. Even if you change the underlying element representing the data the tests run without error. For example to represent the text about workout name one decides to use
<span> instead of
<h6> the data-test-id can remain same as it represents workout name thus producing robust test. We will look at the unit-test now:
It is clear from the test & code above is that we are adding an extra attribute to the code & this attributes are now part of the code we release to production & any data that is not related to application business logic should be avoided & keep things de-cluttered.
There are two ways to achieve this.
Using a Babel Plug-in:
Eject the react app using
npm eject or
yarn eject (only if application is created using create-react-app) & use a babel plugin called
babel-plugin-jsx-remove-data-test-id. If you’ve created app manually then you can use above mentioned plug-in directly. What this plugin does is, it removes data-test-id attribute from the production build.
README.md is very self-explantory for this plug-in. Also if you name your attribute something different e.g.
data-test-attr you can let the plug-in know your custom attribute name to be removed from production build.
Without Ejecting from CRA & Adding Babel Plug-in
Well, in this case we will construct a method that will add data-test-id attribute to a component only if the
NODE_ENV is set to
Now we will use the method above to add
data-test-id attribute to component/element when the
development. Look at the updated code of Workout.jsx here. It won’t have data-test-id for the production build.
This strategy can be used with your selenium UI test(s) as well by adding selenium-id to the elements you want to identify & build robust tests. No matter how big or small is your application, this strategy can be used to write unit-test(s) or UI test(s).