React TDD in 30 Minute - Test Driven Development with Jest and Enzyme

React TDD in 30 Minute - Test Driven Development with Jest and Enzyme

React TDD in 30 Minute - Test Driven Development with Jest and Enzyme. In just 30 minutes, we will know the benefits of TDD and how to follow Test Driven Development in Reactjs by using Jest and Enzymes.

In just 30 minutes, we will know the benefits of TDD and how to follow Test Driven Development in Reactjs by using Jest and Enzymes.

Hire React Js Developer from Expert React JS Development Company

Hire React Js Developer from Expert React JS Development Company

Are you looking to [hire React JS developer](https://www.mobiwebtech.com/react-js-development-company/ "hire React JS developer") from a prestigious and reliable React JS development company? Visit at Mobiweb Technologies here we have a big team...

Are you looking to hire React JS developer from a prestigious and reliable React JS development company? Visit at Mobiweb Technologies here we have a big team of expert React JS developers, you can hire them on an hourly, weekly or monthly basis for the successful completion of your dream project.

Introduction to Test React Components using Jest

Introduction to Test React Components using Jest

In this article, we'll take a look at using Jest — a testing framework maintained by Facebook — to test our React components

In this article, we’ll take a look at using Jest — a testing framework maintained by Facebook — to test our ReactJS components. We’ll look at how we can use Jest first on plain JavaScript functions, before looking at some of the features it provides out of the box specifically aimed at making testing React apps easier. It’s worth noting that Jest isn’t aimed specifically at React: you can use it to test any JavaScript applications. However, a couple of the features it provides come in really handy for testing user interfaces, which is why it’s a great fit with React.

Sample Application

Before we can test anything, we need an application to test! Staying true to web development tradition, I’ve built a small todo application that we’ll use as the starting point. You can find it, along with all the tests that we’re about to write, on GitHub.

The application is written in ES2015, compiled using Webpack with the Babel ES2015 and React presets. I won’t go into the details of the build set up, but it’s all in the GitHub repo if you’d like to check it out. You’ll find full instructions in the README on how to get the app running locally. If you’d like to read more, the application is built using Webpack, and I recommend “A Beginner’s guide to Webpack” as a good introduction to the tool.

The entry point of the application is app/index.js, which just renders the Todos component into the HTML:

render(
  <Todos />,
  document.getElementById('app')
);

The Todos component is the main hub of the application. It contains all the state (hard-coded data for this application, which in reality would likely come from an API or similar), and has code to render the two child components: Todo, which is rendered once for each todo in the state, and AddTodo, which is rendered once and provides the form for a user to add a new todo.

Because the Todos component contains all the state, it needs the Todo and AddTodo components to notify it whenever anything changes. Therefore, it passes functions down into these components that they can call when some data changes, and Todos can update the state accordingly.

Finally, for now, you’ll notice that all the business logic is contained in app/state-functions.js:

export function toggleDone(state, id) {…}

export function addTodo(state, todo) {…}

export function deleteTodo(state, id) {…}

These are all pure functions that take the state and some data, and return the new state. If you’re unfamiliar with pure functions, they are functions that only reference data they are given and have no side effects. For more, you can read my article on A List Apart on pure functions and my article on SitePoint about pure functions and React.

If you’re familiar with Redux, they’re fairly similar to what Redux would call a reducer. In fact, if this application got much bigger I would consider moving into Redux for a more explicit, structured approach to data. But for this size application you’ll often find that local component state and some well abstracted functions to be more than enough.

To TDD or Not to TDD?

There have been many articles written on the pros and cons of test-driven development, where developers are expected to write the tests first, before writing the code to fix the test. The idea behind this is that, by writing the test first, you have to think about the API that you’re writing, and it can lead to a better design. For me, I find that this very much comes down to personal preference and also to the sort of thing I’m testing. I’ve found that, for React components, I like to write the components first and then add tests to the most important bits of functionality. However, if you find that writing tests first for your components fits your workflow, then you should do that. There’s no hard rule here; do whatever feels best for you and your team.

Introducing Jest

Jest was first released in 2014, and although it initially garnered a lot of interest, the project was dormant for a while and not so actively worked on. However, Facebook has invested the last year into improving Jest, and recently published a few releases with impressive changes that make it worth reconsidering. The only resemblance of Jest compared to the initial open-source release is the name and the logo. Everything else has been changed and rewritten. If you’d like to find out more about this, you can read Christoph Pojer’s comment, where he discusses the current state of the project.

If you’ve been frustrated by setting up Babel, React and JSX tests using another framework, then I definitely recommend giving Jest a try. If you’ve found your existing test setup to be slow, I also highly recommend Jest. It automatically runs tests in parallel, and its watch mode is able to run only tests relevant to the changed file, which is invaluable when you have a large suite of tests. It comes with JSDom configured, meaning you can write browser tests but run them through Node, can deal with asynchronous tests and has advanced features such as mocking, spies and stubs built in.

Installing and Configuring Jest

To start with, we need to get Jest installed. Because we’re also using Babel, we’ll install another couple of modules that make Jest and Babel play nicely out of the box:

npm install --save-dev babel-jest babel-polyfill babel-preset-es2015 babel-preset-react jest

You also need to have a .babelrc file with Babel configured to use any presets and plugins you need. The sample project already has this file, which looks like so:

{
  "presets": ["es2015", "react"]
}

We won’t install any React testing tools yet, because we’re not going to start with testing our components, but our state functions.

Jest expects to find our tests in a __tests__ folder, which has become a popular convention in the JavaScript community, and it’s one we’re going to stick to here. If you’re not a fan of the __tests__ setup, out of the box Jest also supports finding any .test.js and .spec.js files too.

As we’ll be testing our state functions, go ahead and create __tests__/state-functions.test.js.

We’ll write a proper test shortly, but for now, put in this dummy test, which will let us check everything’s working correctly and we have Jest configured.

describe('Addition', () => {
  it('knows that 2 and 2 make 4', () => {
    expect(2 + 2).toBe(4);
  });
});

Now, head into your package.json. We need to set up npm test so that it runs Jest, and we can do that simply by setting the test script to run jest.

"scripts": {
  "test": "jest"
}

If you now run npm test locally, you should see your tests run, and pass!

PASS  __tests__/state-functions.test.js
  Addition
    ✓ knows that 2 and 2 make 4 (5ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 passed, 0 total
Time:        3.11s

If you’ve ever used Jasmine, or most testing frameworks, the above test code itself should be pretty familiar. Jest lets us use describe and it to nest tests as we need to. How much nesting you use is up to you; I like to nest mine so all the descriptive strings passed to describe and it read almost as a sentence.

When it comes to making actual assertions, you wrap the thing you want to test within an expect() call, before then calling an assertion on it. In this case, we’ve used toBe. You can find a list of all the available assertions in the Jest documentation. toBe checks that the given value matches the value under test, using === to do so. We’ll meet a few of Jest’s assertions through this tutorial.

Testing Business Logic

Now we’ve seen Jest work on a dummy test, let’s get it running on a real one! We’re going to test the first of our state functions, toggleDone. toggleDone takes the current state and the ID of a todo that we’d like to toggle. Each todo has a done property, and toggleDone should swap it from true to false, or vice-versa.

If you’re following along with this, make sure you’ve cloned the repo and have copied the app folder to the same directory that contains your ___tests__ folder. You’ll also need to install the shortid package (npm install shortid --save), which is a dependency of the Todo app.

I’ll start by importing the function from app/state-functions.js, and setting up the test’s structure. Whilst Jest allows you to use describe and it to nest as deeply as you’d like to, you can also use test, which will often read better. test is just an alias to Jest’s it function, but can sometimes make tests much easier to read and less nested.

For example, here’s how I would write that test with nested describe and it calls:

import { toggleDone } from '../app/state-functions';

describe('toggleDone', () => {
  describe('when given an incomplete todo', () => {
    it('marks the todo as completed', () => {
    });
  });
});

And here’s how I would do it with test:

import { toggleDone } from '../app/state-functions';

test('toggleDone completes an incomplete todo', () => {
});

The test still reads nicely, but there’s less indentation getting in the way now. This one is mainly down to personal preference; choose whichever style you’re more comfortable with.

Now we can write the assertion. First we’ll create our starting state, before passing it into toggleDone, along with the ID of the todo that we want to toggle. toggleDone will return our finish state, which we can then assert on:

const startState = {
  todos: [{ id: 1, done: false, name: 'Buy Milk' }]
};

const finState = toggleDone(startState, 1);

expect(finState.todos).toEqual([
  { id: 1, done: true, name: 'Buy Milk' }
]);

Notice now that I use toEqual to make my assertion. You should use toBe on primitive values, such as strings and numbers, but toEqual on objects and arrays. toEqual is built to deal with arrays and objects, and will recursively check each field or item within the object given to ensure that it matches.

With that we can now run npm test and see our state function test pass:

PASS  __tests__/state-functions.test.js
  ✓ tooggleDone completes an incomplete todo (9ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 passed, 0 total
Time:        3.166s

Rerunning Tests on Changes

It’s a bit frustrating to make changes to a test file and then have to manually run npm test again. One of Jest’s best features is its watch mode, which watches for file changes and runs tests accordingly. It can even figure out which subset of tests to run based on the file that changed. It’s incredibly powerful and reliable, and you’re able to run Jest in watch mode and leave it all day whilst you craft your code.

To run it in watch mode, you can run npm test -- --watch. Anything you pass to npm test after the first -- will be passed straight through to the underlying command. This means that these two commands are effectively equivalent:

  • npm test -- --watch
  • jest --watch

I would recommend that you leave Jest running in another tab, or terminal window, for the rest of this tutorial.

Before moving onto testing the React components, we’ll write one more test on another one of our state functions. In a real application I would write many more tests, but for the sake of the tutorial, I’ll skip some of them. For now, let’s write a test that ensures that our deleteTodo function is working. Before seeing how I’ve written it below, try writing it yourself and seeing how your test compares.

Remember that you will have to update the import statement at the top to import deleteTodo along with toggleTodo:

import { toggleTodo, deleteTodo } from '../app/state-functions';

And here’s how I’ve written the test:

test('deleteTodo deletes the todo it is given', () =&gt; {
    const startState = {
      todos: [{ id: 1, done: false, name: 'Buy Milk' }]
    };

    const finState = deleteTodo(startState, 1);

    expect(finState.todos).toEqual([]);
  });

The test doesn’t vary too much from the first: we set up our initial state, run our function and then assert on the finished state. If you left Jest running in watch mode, notice how it picks up your new test and runs it, and how quick it is to do so! It’s a great way to get instant feedback on your tests as you write them.

The tests above also demonstrate the perfect layout for a test, which is:

  • set up
  • execute the function under test
  • assert on the results.

By keeping the tests laid out in this way, you’ll find them easier to follow and work with.

Now we’re happy testing our state functions, let’s move on to React components.

Testing React Components

It’s worth noting that, by default, I would actually encourage you to not write too many tests on your React components. Anything that you want to test very thoroughly, such as business logic, should be pulled out of your components and sit in standalone functions, just like the state functions that we tested earlier. That said, it is useful at times to test some React interactions (making sure a specific function is called with the right arguments when the user clicks a button, for example). We’ll start by testing that our React components render the right data, and then look at testing interactions. Then we’ll move on to snapshots, a feature of Jest that makes testing the output of React components much more convenient.

To do this, we’ll need to make use of react-addons-test-utils, a library that provides functions for testing React. We’ll also install Enzyme, a wrapper library written by AirBnB that makes testing React components much easier. We’ll use this API throughout our tests. Enzyme is a fantastic library, and the React team even recommend it as the way to test React components.

npm install --save-dev react-addons-test-utils enzyme

Let’s test that the Todo component renders the text of its todo inside a paragraph. First we’ll create __tests__/todo.test.js, and import our component:

import Todo from '../app/todo';
import React from 'react';
import { mount } from 'enzyme';

test('Todo component renders the text of the todo', () => {
});

I also import mount from Enzyme. The mount function is used to render our component and then allow us to inspect the output and make assertions on it. Even though we’re running our tests in Node, we can still write tests that require a DOM. This is because Jest configures jsdom, a library that implements the DOM in Node. This is great because we can write DOM based tests without having to fire up a browser each time to test them.

We can use mount to create our Todo:

const todo = { id: 1, done: false, name: 'Buy Milk' };
const wrapper = mount(
  <Todo todo={todo} />
);

And then we can call wrapper.find, giving it a CSS selector, to find the paragraph that we’re expecting to contain the text of the Todo. This API might remind you of jQuery, and that’s by design. It’s a very intuitive API for searching rendered output to find the matching elements.

const p = wrapper.find('.toggle-todo');

And finally, we can assert that the text within it is Buy Milk:

expect(p.text()).toBe('Buy Milk');

Which leaves our entire test looking like so:

import Todo from '../app/todo';
import React from 'react';
import { mount } from 'enzyme';

test('TodoComponent renders the text inside it', () => {
  const todo = { id: 1, done: false, name: 'Buy Milk' };
  const wrapper = mount(
    <Todo todo={todo} />
  );
  const p = wrapper.find('.toggle-todo');
  expect(p.text()).toBe('Buy Milk');
});

Phew! You might think that was a lot of work and effort to check that “Buy Milk” gets placed onto the screen, and, well … you’d be correct. Hold your horses for now, though; in the next section we’ll look at using Jest’s snapshot ability to make this much easier.

In the meantime, let’s look at how you can use Jest’s spy functionality to assert that functions are called with specific arguments. This is useful in our case, because we have the Todo component which is given two functions as properties, which it should call when the user clicks a button or performs an interaction.

In this test we’re going to assert that when the todo is clicked, the component will call the doneChange prop that it’s given.

test('Todo calls doneChange when todo is clicked', () => {
});

What we want to do is to have a function that we can keep track of its calls, and the arguments that it’s called with. Then we can check that when the user clicks the todo, the doneChange function is called and also called with the correct arguments. Thankfully, Jest provides this out of the box with spies. A spy is a function whose implementation you don’t care about; you just care about when and how it’s called. Think of it as you spying on the function. To create one, we call jest.fn():

const doneChange = jest.fn();

This gives a function that we can spy on and make sure it’s called correctly. Let’s start by rendering our Todo with the right props:

const todo = { id: 1, done: false, name: 'Buy Milk' };
const doneChange = jest.fn();
const wrapper = mount(
  <Todo todo={todo} doneChange={doneChange} />
);

Next, we can find our paragraph again, just like in the previous test:

const p = TestUtils.findRenderedDOMComponentWithClass(rendered, 'toggle-todo');

And then we can call simulate on it to simulate a user event, passing click as the argument:

p.simulate('click');

And all that’s left to do is assert that our spy function has been called correctly. In this case, we’re expecting it to be called with the ID of the todo, which is 1. We can use expect(doneChange).toBeCalledWith(1) to assert this, and with that we’re done with our test!

test('TodoComponent calls doneChange when todo is clicked', () => {
  const todo = { id: 1, done: false, name: 'Buy Milk' };
  const doneChange = jest.fn();
  const wrapper = mount(
    <Todo todo={todo} doneChange={doneChange} />
  );

  const p = wrapper.find('.toggle-todo');
  p.simulate('click');
  expect(doneChange).toBeCalledWith(1);
});

Better Component Testing with Snapshots

I mentioned above that this might feel like a lot of work to test React components, especially some of the more mundane functionalities (such as rendering the text). Rather than make a large amount of assertions on React components, Jest lets you run snapshot tests. These are not so useful for interactions (in which case I still prefer a test like we just wrote above), but for testing that the output of your component is correct, they’re much easier.

When you run a snapshot test, Jest renders the React component under test and stores the result in a JSON file. Every time the test runs, Jest will check that the React component still renders the same output as the snapshot. Then, when you change a component’s behavior, Jest will tell you and either:

  • you’ll realize you made a mistake, and you can fix the component so it matches the snapshot again
  • or, you made that change on purpose, and you can tell Jest to update the snapshot.

This way of testing means that:

  • you don’t have to write a lot of assertions to ensure your React components are behaving as expected
  • you can never accidentally change a component’s behavior, because Jest will realize.

You also don’t have to snapshot all your components. In fact, I’d actively recommend against it. You should pick components with some functionality that you really need to ensure is working. Snapshotting all your components will just lead to slow tests that aren’t useful. Remember, React is a very thoroughly tested framework, so we can be confident that it will behave as expected. Make sure you don’t end up testing the framework, rather than your code!

To get started with snapshot testing, we need one more Node package. react-test-renderer is a package that’s able to take a React component and render it as a pure JavaScript object. This means it can then be saved to a file, and this is what Jest uses to keep track of our snapshots.

npm install --save-dev react-test-renderer

Now, let’s rewrite our first Todo component test to use a snapshot. For now, comment out the TodoComponent calls doneChange when todo is clicked test as well.

The first thing you need to do is import the react-test-renderer, and also remove the import for mount. They can’t both be used; you either have to use one or the other. This is why we have commented the other test out for now.

import renderer from 'react-test-renderer';

Now I’ll use the renderer we just imported to render the component, and assert that it matches the snapshot:

describe('Todo component renders the todo correctly', () => {
  it('renders correctly', () => {
    const todo = { id: 1, done: false, name: 'Buy Milk' };
    const rendered = renderer.create(
      <Todo todo={todo} />
    );
    expect(rendered.toJSON()).toMatchSnapshot();
  });
});

The first time you run this, Jest is clever enough to realize that there’s no snapshot for this component, so it creates it. Let’s take a look at __tests__/__snapshots__/todo.test.js.snap:

exports[`Todo component renders the todo correctly renders correctly 1`] = `
<div
  className="todo  todo-1">
  <p
    className="toggle-todo"
    onClick={[Function]}>
    Buy Milk
  </p>
  <a
    className="delete-todo"
    href="#"
    onClick={[Function]}>
    Delete
  </a>
</div>
`;

You can see that Jest has saved the output for us, and now the next time we run this test it will check that the outputs are the same. To demonstrate this, I’ll break the component by removing the paragraph that renders the text of the todo, meaning that I’ve removed this line from the Todo component:

<p className="toggle-todo" onClick={() => this.toggleDone() }>{ todo.name }</p>

Let’s see what Jest says now:

FAIL  __tests__/todo.test.js
 ● Todo component renders the todo correctly › renders correctly

   expect(value).toMatchSnapshot()

   Received value does not match stored snapshot 1.

   - Snapshot
   + Received

     <div
       className="todo  todo-1">
   -   <p
   -     className="toggle-todo"
   -     onClick={[Function]}>
   -     Buy Milk
   -   </p>
       <a
         className="delete-todo"
         href="#"
         onClick={[Function]}>
         Delete
       </a>
     </div>

     at Object.<anonymous> (__tests__/todo.test.js:21:31)
     at process._tickCallback (internal/process/next_tick.js:103:7)

Jest realized that the snapshot doesn’t match the new component, and lets us know in the output. If we think this change is correct, we can run jest with the -u flag, which will update the snapshot. In this case, though, I’ll undo my change and Jest is happy once more.

Next we can look at how we might use snapshot testing to test interactions. You can have multiple snapshots per test, so you can test that the output after an interaction is as expected.

We can’t actually test our Todo component interactions through Jest snapshots, because they don’t control their own state but call the callback props they are given. What I’ve done here is move the snapshot test into a new file, todo.snapshot.test.js, and leave our toggling test in todo.test.js. I’ve found it useful to separate the snapshot tests into a different file; it also means that you don’t get conflicts between react-test-renderer and react-addons-test-utils.

Remember, you’ll find all the code that I’ve written in this tutorial available on GitHub for you to check out and run locally.

Conclusion

Facebook released Jest a long time ago, but in recent times it’s been picked up and worked on excessively. It’s fast become a favorite for JavaScript developers and it’s only going to get better. If you’ve tried Jest in the past and not liked it, I can’t encourage you enough to try it again, because it’s practically a different framework now. It’s quick, great at rerunning specs, gives fantastic error messages and tops it all off with its snapshot functionality.

If you have any questions please feel free to raise an issue on GitHub and I’ll be happy to help. And please be sure check out Jest on GitHub and star the project; it helps the maintainers.

Semantic tests with React Testing Library

Semantic tests with React Testing Library

Learn how to use React Testing Library to write tests that allow you to test what your application should do rather than how it achieves its aim.

Testing code can be a controversial subject, largely due to the multitude of ways one can go about writing a test.

There are no clear rules, and ultimately you are the one in charge of deciding what’s worth testing and how you’re going to do it.

One common mistake is to test implementation details, but perhaps you’ve read that already.

Let me take a step back then — what is the end goal of a test?

Writing user stories

A common software abstraction is to write user stories — that is, possible actions that a user can take when interacting with your application.

Suppose you are to build a Celsius-to-Fahrenheit converter.

A legitimate story could be something like:

“As a user, I want to be able to convert from Celsius to Fahrenheit.”

Naturally, as a careful developer, you want to assert that for a given set of numbers and inputs the conversion works (or it fails gracefully for invalid inputs like “banana.”)

Note, however, that testing that a function is able to successfully handle the conversion from Celsius to Fahrenheit is only half the story.

If you are able to perform the most expensive and relevant calculation but your end-user can’t access it, all effort will be in vain.

Why is that?

Well, as a front-end developer, your job is to not only ensure users get the correct answers to their questions but also to make sure they can use your application.

Therefore, you need to assess that the user has interacted with your application as expected.

In our example, that means that somewhere in the screen you expect some text to be displayed like this: “25ºC equals to 77ºF.”

Now, that’s a relevant test. You just assessed that, for a given input, the user satisfactorily got the right answer on the screen.

Stories (mostly) don’t care about details

The main takeaway here is that the user stories aren’t centered on your development implementations, so your tests shouldn’t be, either.

Of course, the scenarios in question are related to application-wide tests (things that have context), not bare-bones libraries.

If your goal is to create a library that converts Celsius to Fahrenheit to Kelvin, then it’s fine to test the details once you are detached of context.

Now that we understand that tests should resemble user stories, you can predict where semantics come from.

At the end of the day, your tests should have clear semantics such that you could read them in plain English—the same way you describe user stories.

We’ll see how we can leverage the React Testing Library API to write semantic tests that make sense.

Case study: Temperature Converter

Let’s dive further into the Temperature Converter application.

We’ll pretend that a competent Project Manager heard the complaints of their clients (probably any non-American who has moved recently to the US) and came up with the following requirements:

  • As a user, I want to be able to convert from Celsius to Fahrenheit
  • As a user, I want to be able to convert from Fahrenheit to Celsius
  • As a user, I want to click on a Reset button so I can easily convert many values with minimal effort.

Apart from the lack of creativity of the PM when writing stories, the requirements are pretty straightforward.

We will sketch a simple app, do a good ol’ smoke test to check that everything looks alright, and then apply what we just learned in order to write better tests.

Consider the following CodeSandbox for our sample application:
https://blog.logrocket.com/semantic-tests-with-react-testing-library/

Diving into the specifics of the code is beyond the scope of this article (check How to Reuse Logic With React Hooks for more context on how to use Hooks to create React applications).

However, the code should be pretty straightforward. We are basically requiring user input and allowing them to convert from Celsius to Fahrenheit or vice-versa.

We then display the results and a Reset button shows up. Upon clicking the button, the input is cleared and regains focus.

This aligns with what our users are looking for: we’ll improve the usability of the app and, most importantly, preserve its accessibility.

Now that we have a live application that seems to work, let’s be responsible developers and write some tests.

We’ll try to match each user story to a single test. By doing that, we will be confident that each requirement is being fulfilled with a set of tests backing us up.

Consider this basic skeleton for App.test.js:

import React from "react";
import { cleanup } from "@testing-library/react";

afterEach(cleanup);

test("user is able to convert from celsius to fahrenheit", () => {
  /* story 1 goes here */
});

test("user is able to convert from fahrenheit to celsius", () => {
  /* story 2 goes here */
});

test("user can reset calculation and automatically focus on the input", () => {
  /* story 3 goes here */
});

(We are using Jest as our test runner, but that’s not relevant to the main point presented in the article.)

Notice that our three tests are really straightforward and any failures in them would quickly expose what is really going on.

Now we’ll leverage RTL and write the first test in a way that makes sense:

import React from "react";
import App from "./App.js";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

afterEach(cleanup);

test("user is able to convert from celsius to fahrenheit", () => {
  render(<App />);
  const input = screen.getByLabelText("Temperature:");
  userEvent.type(input, "25");
  expect(screen.getByText("25ºC equals to 77ºF")).toBeTruthy();
  userEvent.type(input, "0");
  expect(screen.getByText("0ºC equals to 32ºF")).toBeTruthy();
  userEvent.type(input, "banana");
  expect(screen.queryByTestId("result")).toBeFalsy();
});

/* code goes on */

There are a couple things to notice with the dependencies:

First, we import the component in question App.js.

Then, notice that we are importing render and screen from RTL. While the first has been around since the library’s first launch, screen is a new addition shipped on version 9.4.0. We will see its main advantage shortly.

We also import a new dependency, [userEvents](https://github.com/testing-library/user-event), straight from @testing-library/user-event. This library will boost our test readability and help us achieve our goal of improving our semantics.

Let’s actually dive into the test. If you are used to RTL, the first thing you’ll notice is that render is not returning anything. In fact, that’s the main advantage of importing screen.

What screen does is basically expose all queries that allow you to select elements in the screen (hence the name).

This is a pretty good change because it helps you avoid bloating the test with lots of destructuring, which is always annoying when you are not sure yet which queries to use.

Also, the code looks cleaner. (Note: there’s still a case for de-structuring container and rerender as mentioned by Kent C. Dodds in this tweet.)

The other difference from conventional tests you might have been writing is the userEvent object.

This object provides a handful of user interactions that are semantically understandable and conceal implementation details. Consider the following example:

// Previously
fireEvent.change(input, { target: { value: "25" } });

// With userEvents
userEvent.type(input, "25");

Not only is our code is shorter, but it also makes much more sense now.

Remember that our goal is to write a test as close as possible to plain English. By encapsulating implementation details, userEvent really puts us on the right track.

If you are curious, go ahead and check their documentation.

Once we are able to fill the input, we can now assert that the correct text is being displayed.

Now we can test a bunch of other options and confirm that what is displayed in the screen is expected (e.g. an invalid input like banana won’t work).

Note: in a modular application, the conversion functions could be extracted in their own file and have their own tests (with many more test scenarios).

If you test the function separately, there’s no need to make redundant checks in the user stories as well (test is code and you want it maintainable as such).

With a test that is only 8 lines long, we were able to check that our first scenario works as expected.

Let’s jump into our second user story — convert from Fahrenheit to Celsius (maybe a New Yorker having some fun on a beach in South America).

The test should be pretty similar to our first one, with a single caveat: we need to make sure that the user has selected the right option.

test("user is able to convert from fahrenheit to celsius", () => {
  render(<App />);
  const fahrenheitOption = screen.getByLabelText("Fahrenheit to Celsius");
  userEvent.click(fahrenheitOption);
  const input = screen.getByLabelText("Temperature:");
  userEvent.type(input, "77");
  expect(screen.getByText("77ºF equals to 25ºC")).toBeTruthy();
  userEvent.type(input, "32");
  expect(screen.getByText("32ºF equals to 0ºC")).toBeTruthy();
  userEvent.type(input, "banana");
  expect(screen.queryByTestId("result")).toBeFalsy();
});

That’s it. By leveraging userEvent again, emulating a click event becomes trivial.

Our code is perfectly readable and guarantees that the reverse direction (F to C) works as expected.

Our third and final test is slightly different — now our goal is to test the user experience rather than whether or calculator works.

We want to make sure that our application is accessible and that users can rapidly test several values:

test("user can reset calculation and automatically focus on the input", () => {
  render(<App />);
  const input = screen.getByLabelText("Temperature:");
  userEvent.type(input, "25");
  expect(screen.queryByTestId("result")).toBeTruthy();
  const resetButton = screen.getByText("Reset");
  userEvent.click(resetButton);
  expect(screen.queryByTestId("result")).toBeFalsy();
  expect(document.activeElement).toBe(input);
});

There you have it. We basically made three checks:

  • Whenever a user adds some input, there’s a result displayed (the actual message shown is omitted from the test once this isn’t what is being checked here)
  • When the Reset button is clicked, the result is not there anymore
  • The focus on the screen is back to the input

One of my favorite things about RTL is how easy it is to assert where a focus really is.

Notice how semantic expect(document.activeElement).toBe(input) is. That pretty much looks like plain English to me.

And that’s it. Our three stories are covered, the Project Manager is happier, and hopefully our tests will keep the code clean for a long time.

Conclusion

The aim of this article was to expose the recent modifications in the React Testing Library’s API and show you how you can explore it to write better tests for you and your team.

I feel way more confident when I write tests that I understand because I stop chasing meaningless metrics (e.g. code coverage) to pay attention to what really matters (e.g. if my designed scenario works as expected).

React Testing Library was a big step in the right direction, mainly if you have some Enzyme background (in which case you might want to check React Testing Library Common Scenarios, where I explore how you tackle everyday scenarios in a React application).

It really facilitates to test what your application should do rather than how it does it. The semantics make a difference.

Originally published by Rafael Quintanilha at https://blog.logrocket.com