Using Jest and React Testing Library to Test React Components

In this lesson, we'll talk about the two main testing tools we'll use to test our React components - Jest & React Testing Library.

This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two with a single-time purchase.

Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two
  • [00:00 - 00:15] In this lesson, we'll talk about the two main tools that we're going to use to establish unit tests in our client application. And these two tools are the Jest Testing Framework and the React Testing Library.

    [00:16 - 00:25] We'll first begin by talking a little bit about Jest. Jest is a JavaScript Testing Framework and Runner.

    [00:26 - 00:35] It allows us to specify our test suites with "Describe and It Blocks". Let's quickly go to a codebase to illustrate what we're talking about here.

    [00:36 - 00:55] We've mentioned this in the previous lesson and how we can sort of structure our tests, but here is a good apparent view of an actual file contributing to the test of a certain component. We won't spend time talking about the details, but from the get-go we can see that we're structuring our test with a "Describe" block.

    [00:56 - 01:06] And in each of these separate describe blocks, there exists these "It Blocks" that allow us to specify each of the unit tests that we want. This comes from Jest.

    [01:07 - 01:14] Jest is giving us the capability to structure our tests with these functions. Additionally, Jest is a test runner.

    [01:15 - 01:24] It gives us scripts to actually run and execute our tests. So here's an example of us actually executing our tests for that particular file.

    [01:25 - 01:43] The Jest framework will give us information on the suite that the test is running for, the number of tests that have passed or failed, and any other additional information as well. Jest also comes with a large suite of "Expect Assertions" or "Matchers".

    [01:44 - 01:55] Let's quickly go through the Jest documentation for a second. In this piece of the documentation, it tells us that when we write our tests, we often need to check that values meet search and conditions.

    [01:56 - 02:07] And the "Expect" function or functionality gives us access to a number of "Mat chers" that allow us to validate different things. And here's a list of all the different methods or matches.

    [02:08 - 02:11] We can expect something. We can expect something has assertions.

    [02:12 - 02:16] We can expect something to not be something. We can expect something to be of a certain value.

    [02:17 - 02:21] We can expect something to have been called. We can expect something to have returned.

    [02:22 - 02:26] We can expect something to be defined. Likewise something to be undefined.

    [02:27 - 02:38] We can expect something to be faulty, to be truthy, and on and on and on. Most importantly, it isn't necessary for one to fully memorize or recognize each of these particular "Matchers".

    [02:39 - 02:49] It's helpful to know or have this as a reference when tests are being written. Because in certain cases as you're writing your tests, you may find yourself in a situation to specify certain things.

    [02:50 - 02:58] Maybe you want to see if something is defined, whether you want to see something has a certain length. And this is where these "Matchers" are incredibly helpful and can be used.

    [02:59 - 03:12] It also comes with other incredibly useful features, like mocking supports or snapshot testing. With "Mocks", we're able to stub out functionality with "Mock" data in our tests.

    [03:13 - 03:32] We can mock user-specific modules, node modules, functions, etc. With "Snapshot Testing", it's another feature that Jess provides that allows us to take a snapshot of our React components, such that our tests can now depend on these snapshots to assert what they're looking for.

    [03:33 - 03:54] We're not going to spend a lot of time doing snapshot testing or at all in our specific tests that we write, but I just want to illustrate that Jess provides a lot of these different capabilities to make sure that we can write our tests in a very good manner. And a very good closing point in why Jess is super cool, it has no or practically minimal configuration and setup.

    [03:55 - 04:02] And additionally, it comes with all of these functionality together. We don't have to install other testing libraries or assertion libraries.

    [04:03 - 04:21] We're able to have a framework, a runner, our assertions, mocking supports, snapshot testing, etc. all in this library alone. The other big piece or tool that we'll use in our unit tests is the React Testing Library.

    [04:22 - 04:48] React Testing Library is just a set of helper methods that help test our React components. It isn't a framework, it's not a runner, it isn't provided us with any of the structure that we need to create our actual test files, it just provides useful and very helpful methods that make testing React components a lot easier or a lot more simpler to do.

    [04:49 - 05:16] The key aspect behind React Testing Library is it provides us with these methods and utilities to help test our components without focusing on implementation details. We'll talk a little bit more about this in the end of this lesson, but there is some significant advantages with this approach and there's some drawbacks with the way we've already built our tiny house application and the way we should be using React Testing Library.

    [05:17 - 05:41] And last but not least, React Testing Library works with any testing framework. So you can use Jest, you can use Mocha, you can use other frameworks that exist in the JavaScript scene, at the end of the day React Testing Library isn't intended to replace any of these, it's intended to work with these frameworks to help assert and run our tests for our React components.

    [05:42 - 06:07] Now that we have an idea of the two separate tools that we're going to use, let 's see how these tools work together to allow us to actually establish tests. And an example that we'll go with is a very simple component that acts as a counter and this counter component simply displays or presents a counter value and it has a button that when clicked allows you to increment this counter value in the UI.

    [06:08 - 06:30] This example is directly from the React documentation and it's incredibly straightforward. We just use the useState hook, we initialize the counter value with zero, we destruct a counter property and the function called setCount to update this property, we display this particular counter value in the actual template and we finally we have a button that whenever clicked simply increments this particular counter value.

    [06:31 - 06:48] When this button is clicked the state property is updated, the UI gets re- rendered and the user is able to see the number go higher from zero to one to two and so forth and so on. Unit tests for a component like this could be structured something along the following.

    [06:49 - 07:02] Now a quick tangential point, we've mentioned how unit testing is the testing of specific units in our application. React components are units of their own, they have their own input and their own output.

    [07:03 - 07:23] So a very common pattern that is often done when it comes to testing react components is creating a specific file for that component in question. So here assume we're creating a file called app.test.tsx and we're importing the react utility and we're also importing the app component that is to be tested.

    [07:24 - 07:35] And then we'll specify a describe block for this specific component. And this tells us that we're going to be specifying all the tests that we want for this component in question.

    [07:36 - 07:51] We're not going to write a test here that uses another specific component and only that component alone. All the tests that actually are being done here will conform to this specific unit or in other words, this specific react component.

    [07:52 - 08:11] Now two tests that we can perhaps think about for this counter example is one, we can try to assert or specify that when this particular component is first rendered or initialized, it will display initial counter value of zero. And that's what a test that we actually want to say.

    [08:12 - 08:21] We want to say, hey, this component in the very beginning would show a value of zero, not another number. Another test would be more around the functionality of this component.

    [08:22 - 08:36] We want to assert that it acts as the counter as we expected to. And we wanted to say that if this particular button in a component is clicked, it would increment the count within this component by one.

    [08:37 - 08:53] Now the very first thing we'll most likely want to do for any of these tests for this particular component is we'll want to render the component in our test. Components, specifically function components and what we tend to use now, are just functions that take an input and return an output.

    [08:54 - 09:09] In our tests, we want to assert that the output of this function, the component template or the component itself behaves the way we want to. So pretty much in all of these tests, when we arrange our tests, we need to actually render this component.

    [09:10 - 09:16] To do so, we can use the react testing library. Assume we have this testing library installed.

    [09:17 - 09:27] We can import a render function that allows us to render our components in our tests. The render function allows us to render these components in their tests.

    [09:28 - 09:50] But this often creates a wrapper for this particular component for another very useful capability. This is because from this particular render function from react testing library , we'll be able to destruct or obtain very useful queries that we can use to actually inspect and arrange the pieces in our component.

    [09:51 - 10:05] Before we talk about the different queries that exist, if we go back to our actual component that we're looking at. In our very first test that we mentioned, we wanted to assert that this component would render an initial count of zero.

    [10:06 - 10:28] So when we actually have this component in our test, we're going to need to find the specific element in the rendered template that we can inspect and assert that, hey, this particular piece of the component has a value of zero. The second test that we've mentioned was we want to actually trigger a button click in our test and assert that the value is incremented by one.

    [10:29 - 10:42] To do so, in our test, we need to actually find the button in the rendered outputs. And from this button, we need to actually act on it and actually specify that the change that we expect is what we see.

    [10:43 - 10:53] To find or query for these particular elements, this is where the react testing library helps us. It gives us a series or a list of very useful queries to query what we're looking for.

    [10:54 - 11:07] We can use these query functions like get by role, get by label text, get by placeholder text, et cetera. Let's spend a little time talking about these queries.

    [11:08 - 11:23] These queries essentially exist to allow us to find the nodes we're looking for in our component template. Now as a quick FYI, these queries come from a library called DOM testing library.

    [11:24 - 11:41] This is a very lightweight library that the react testing library depends upon. So again, even though here we see that these queries refer to this, the react testing library is a not a library that depends on the DOM testing library for all the functionality such as queries and events, et cetera.

    [11:42 - 11:53] So as an example, a particular query would be to find something based on its label text. So in our rendered templates, assume that our component had a label and an input.

    [11:54 - 12:08] And this is essentially good practice when you want to specify a form and you want to specify what each input is responsible for. So here in this documentation, there's an example for a username input, the label that constitutes that particular username.

    [12:09 - 12:18] This particular query by label text would allow us to query for this input node based on the label. So assume that we render a component.

    [12:19 - 12:28] We can say, give me this input node by getting the label text that we're looking for. Here's another query by placeholder text.

    [12:29 - 12:38] Instead of trying to find an input based on its label, we can try to find an input node based on the placeholder value. Very similar thinking process, but slightly different.

    [12:39 - 12:51] Here we just simply say, give me the placeholder text of this particular placeholder. And this would return the input node in which the placeholder text matches what you specify.

    [12:52 - 13:06] Or I guess a parent would might be trying to find something purely based on its text. So assume we had an anchor element as displayed in this documentation here that allows us to basically when clicked takes a user to an about page.

    [13:07 - 13:23] If we want to find this particular node in our test, we could run the get by text query and we can specify what we're looking for to give us the node that we want. And this continues, we can get something based on its alt text, accessibility alt text.

    [13:24 - 13:31] We can get something based on a title. We can get something based on a display value and so forth and so on.

    [13:32 - 13:47] All these different queries allow us to essentially query for the different nodes in our template. Now a quick tangential comment or a tangential note is you'll notice that for each of these queries, perhaps there are different actual functions that can be used.

    [13:48 - 13:59] You can say get by display value, query by display value, get all by display value, etc. With the React testing library, these are known as variants to queries.

    [14:00 - 14:11] And they all do slightly different things to one another. The get by variants gives us the very first matching node for a query, but throws an error if no elements are matched.

    [14:12 - 14:23] The get all by variant returns an array of matching nodes. So if you want to assume a bunch of inputs or a bunch of text elements, we can use the get all by variant.

    [14:24 - 14:37] The query by variant gives us the first matching node for a query, but returns a no if no elements are matched as opposed to throwing an error like the get by variant. There's also the query all by variant.

    [14:38 - 14:46] There's a find by variant which essentially returns a promise that then resolves to an element. Each of these variants can be used for each of these queries.

    [14:47 - 14:56] So you can say get by label text or you can say find by label text. Similarly, we can say query by text or you can say find all by text.

    [14:57 - 15:04] Right? So there's just a way to mix and match the specific queries you're looking for as well as the variations and how you actually want to make those queries.

    [15:05 - 15:11] Do you want a single node? Do you want a list of nodes or do we want an actual promise that gets resolved asynchronously?

    [15:12 - 15:17] Okay. So now with this understood, let's go back to our slides.

    [15:18 - 15:31] The one last thing to mention here is the React testing library also provides helper functions to help trigger events. We'll talk about this in a brief second as we go through our example.

    [15:32 - 15:53] So when we go back to our example, in the very first test, we've mentioned that we want to assert, we'll go back to the component in question, we want to assert that the count value displayed in this particular text initially would say zero. So the first thing we need to actually obtain our query for is the actual node in which this is displayed.

    [15:54 - 15:59] In this case, we can see that there isn't much information to query for except for the text. Right?

    [16:00 - 16:14] We can say query by text or get by text or whatever variation we're looking for to try to get this node in our test. So here in our particular test, we can say query by text is the function or query we're looking for.

    [16:15 - 16:25] This would give us the actual node if found or return null if it isn't fine what we're looking for. What are we trying to query for in this context?

    [16:26 - 16:35] We're trying to query for the text that we think the component will be initialized with. We want to query for the string or the message that says you clicked zero times .

    [16:36 - 16:46] This asserts or this would specify in our assertion that this particular number zero is being shown at the very beginning. How do we come up with our assertion here?

    [16:47 - 16:53] We're simply saying that we expect this particular query to not be null. What does this mean or why are we doing this?

    [16:54 - 17:06] Because we know that the query by text helper or query would return null if it can't find what we're looking for. We know that our component is incredibly simple and it doesn't return a bunch of messages like this.

    [17:07 - 17:16] It returns only one single message. So if this can't be found, this would most likely tell us that this particular message doesn't show us the number zero.

    [17:17 - 17:28] Because everything else is static, this is the only dynamic functionality. A quick note to make here, the way we actually come up with our assertion is completely dependent on how one feels like it.

    [17:29 - 17:43] Instead of saying this is not to be null, perhaps you can say I expect this query to have a value equal to the string where I expect this get by function query to give me a value equal to this. There's many different ways of doing this.

    [17:44 - 17:55] I often tend to follow this nice pattern of just specifying whether something exists or not. And this tells me if this is not null, it basically means that the initial message that is shown is what I'm expecting.

    [17:56 - 18:01] You clicked zero times. Okay, so now this test is prepared.

    [18:02 - 18:12] Let's try to work on the other test. The other test is where we wanted to make sure that if the button was clicked in our component, it would increment the count by one.

    [18:13 - 18:27] We'll still destruct or obtain the query by text helper, which will allow us to find the actual node that says you clicked a number of times or you clicked two times or whatever. We'll also destruct or obtain another query.

    [18:28 - 18:43] In this case called get by role, the by role query from react testing library allows us to find nodes based on their roles. So whether they're a button, whether it's an anchor tag, whether it is an input element.

    [18:44 - 18:56] So in this context, we'll use this particular query to find the button that we 're looking for. So we'll say get by role button and this will give us the node of the actual button elements.

    [18:57 - 19:11] Now, you might already see what's actually happening here because it's pretty evidence, but you'll notice that in this particular test, we actually want to make an act , not like the test before. The test before is we wanted to assert how the component was initialized.

    [19:12 - 19:19] In this case, we want to assert how this component behaves when a user clicks the button. So we actually need to trigger an event.

    [19:20 - 19:27] How do we trigger this event in our test? We can use the fire event utility helper from the react testing library.

    [19:28 - 19:47] This is a very useful helper that gives us a suite of actual methods that allow us to exhibit or actually construct the functionality or the events we want to trigger. So here, we're essentially saying, run the click event, which is basically click the node of the button.

    [19:48 - 20:07] So again, from the testing perspective, we're saying, give me the node of the button and run the click function from the fire event helper. But from a more apparent way to look at this, we're essentially saying, click the actual button or the very first node, the very first button node in our template.

    [20:08 - 20:14] Once we do this, we can now finally do our assertion. We can assert and see what we're looking for here.

    [20:15 - 20:30] We can make our assertion in such a way that we want to ensure that the text does not say you click zero times, but it instead says you clicked one time, one times, I guess, not the best of English. But I think the idea is understood.

    [20:31 - 20:42] Notice how we're writing our assertions here. We're relying on the fact that the query by variant would return null if it can 't find something and it won't return null if it does find something.

    [20:43 - 20:54] So we're basically saying try to find a node that says you clicked zero times and we want to ensure that this is now null. In other words, we want to ensure that this node can't be found.

    [20:55 - 21:04] And likewise, we're trying to say we do expect the node that says you clicked one times to not be null. In other words, to be present.

    [21:05 - 21:09] Do we need to do two separate expect assertions here? Not necessarily.

    [21:10 - 21:15] You can simply do, I expect this to be null. And that tells us right away that it doesn't say you clicked zero times any longer.

    [21:16 - 21:29] Or you can say, I expect this to just be here to say you clicked one time. By having both in this context, the takeaway that I was trying to go with was we want to confirm that not only does it say you clicked zero times any longer, that is now null.

    [21:30 - 21:40] We also want to verify that it says one time, not two times or three times. The way you want to finally come up with these assertions is totally dependent on how you want to style your actual statements.

    [21:41 - 21:48] But in this context, the takeaway should remain. You want to assert what we're looking for is what we get.

    [21:49 - 22:03] One closing point to make here, in this particular test, you can now neatly see how we're arranging, acting and asserting. We arrange our test by rendering the components and getting the actual helpers or queries that we need.

    [22:04 - 22:18] We act in this test by actually triggering the click event on the button. And finally, we make our assertions, which is always the last step of our test, to assert that what we're looking for or not looking for is what we expect.

    [22:19 - 22:31] Okay, assuming our tests was prepared correctly, when there was no mistakes, our tests should pass. And by passing, they tell us that our component works the way we expect it to.

    [22:32 - 22:51] If at some point during the development journey, this particular app component somehow gets changed in terms of its functionality, our tests that pertain to that change should then fail to reflect that something has been changed. If that change is warranted, then our tests should be changed to reflect that.

    [22:52 - 23:04] If not, we should then fix the component to make sure that our tests then pass. Okay, at this point, we can pretty much conclude what we wanted to actually explain or talk about in this lesson.

    [23:05 - 23:15] At this moment, you should be somewhat familiar with what Jess does and with what React testing library does. And when we actually start to write some of our tests for our app, this will become more and more apparent.

    [23:16 - 23:37] The one last thing we should probably talk about, which I think is a pretty important piece of knowledge is when it comes to the React testing library, what query should you use? Should you find a node based on its placeholder, if it's an input based on its label, based on its text, based on its role, like what should take priority?

    [23:38 - 23:57] The answer to this question is important because it essentially answers the way we're expected to structure our tests with the help of React testing library. And the priority for which queries should be used essentially talks about something that they've labeled as the guiding principles of this library.

    [23:58 - 24:18] This is pretty important and pretty helpful to understand. In this particular library, some of the principles that they stress is number one, if something relates to rendering components, then the utilities that this particular library provides should deal with DOM nodes rather than component instances.

    [24:19 - 24:25] What do we mean by this or what does that mean essentially? Component instances refer to things that are relevant to the component.

    [24:26 - 24:35] State, props, the functions being used, etc. This particular principle states that the utilities provided shouldn't necessarily deal with what's in the components.

    [24:36 - 24:49] It shouldn't matter if the component is using five different state properties or five functions or ten functions or it's importing three other components. What this library encourages us to do is it should focus on DOM nodes.

    [24:50 - 24:57] So we should be interested in seeing the output of the components. This ties into the second points.

    [24:58 - 25:15] It stresses that utilities should be useful for testing the application components in a way that the user would use it. As again, this ties into the fact that we're interested into how the DOM nodes are being shown and how the user uses this.

    [25:16 - 25:24] When a user looks at an application, it doesn't care about the name of a component, how this component was structured in the world of React. It just cares about the actual output.

    [25:25 - 25:27] Is the output semantic? Does it have a label?

    [25:28 - 25:35] Is it accessible? And lastly, the third guiding principle is that these utility implementations should be simple and flexible.

    [25:36 - 25:42] And I really do think they are. I think this library is really cool because it's a very simple library and a somewhat flexible library as well.

    [25:43 - 25:51] So if we go back to the question before, which query should someone use? They actually set up a priority in which the order should be used.

    [25:52 - 26:05] The very first query that should be encouraged to be used right away are the queries accessible to everyone. These are the queries that would reflect the experience of visual or mouse users, as well as those by using assistive technology.

    [26:06 - 26:15] So queries like GetByRoll, give me the button, give me the anchor tag, getBy LabelText. Useful for form fields.

    [26:16 - 26:25] If you have a label input, instead to find this input, you should essentially find it by its label text. This encourages good accessible patterns for forms.

    [26:26 - 26:28] GetByPlaceholderText. That's another alternative that can be used.

    [26:29 - 26:39] GetByText. If you want to find something that's specific to a span or a paragraph or a div , the first thing that a user often defines this is by its actual text.

    [26:40 - 26:44] And then there's GetByDisplayValue, the actual value of a form element. That's number one.

    [26:45 - 26:58] That's their first priority or the first list of queries someone should adhere to. The second piece of queries that someone could adhere to is the ones in which that they are HTML5 or ARIA-complied selectors.

    [26:59 - 27:02] So GetByOutText. GetByTitle.

    [27:03 - 27:17] In this particular instance, the user experience of interacting with these attributes vary greatly across browsers and assistive technologies. So they're not as encouraged as number one, but they're encouraged as being the next pieces of queries that should be followed.

    [27:18 - 27:37] And the last thing that we can use is GetByTestID. This is a specific query in which we can actually add test IDs to the elements in our UI such that the user does not see them, the user does not hear them, but at least we can try to obtain these particular nodes in our tests.

    [27:38 - 27:58] So this is only recommended for cases where we just can't match by role or text or it doesn't make any sense. Okay, so now we know that React Testing Library encourages us to write our test and to use utilities in such a way that the user would go through an application.

    [27:59 - 28:14] When we built our tiny house application, we used a design framework and design to help create our UI. This was incredibly helpful because it allowed us to actually adhere to a consistent CSS pattern.

    [28:15 - 28:28] We didn't have to write a lot of custom CSS. A lot of these components are actually beautifully designed, so we were able to skip all the design and set up for this actual UI by just simply being able to plug and play these components.

    [28:29 - 28:37] So those were some of the significant advantages of using AdDesign. However, when it comes to using it with React Testing Library, there's a bit of challenge in between.

    [28:38 - 28:48] Why? Because when we use a design framework like AdDesign, we give this particular framework responsibility for rendering the actual UI or the actual DOM notes.

    [28:49 - 28:59] Right? So when we render, let's say, a card, this particular card components from Ad Design is the one responsible in rendering title, span, text, image, et cetera.

    [29:00 - 29:17] We know that React Testing Library is targeted towards testing the UI or the pieces of the DOM notes. Now, if AdDesign, for example, adheres to good accessibility patterns and actually renders the output in a semantic manner, there shouldn't be much of a problem.

    [29:18 - 29:41] However, from what I've seen, it doesn't seem to do a lot of good in that aspect or in that space. This starts to become a bit of a challenge when it comes to testing because there were certain times where I was trying to assert a simple component or a simple piece of work that we did and I want to verify a DOM note and I just can't find it purely for the fact that AdDesign decided to make things a little bit more complicated.

    [29:42 - 29:47] I think, for example, the date picture was a very good example of this. There's a lot of asynchronous UI loading here.

    [29:48 - 29:51] There's a lot of hidden UI elements. It's incredibly powerful.

    [29:52 - 29:57] It's beautiful. But when it comes to actually testing it, I noticed there was a few hurdles to come across.

    [29:58 - 30:04] Now, I don't want to make this seem like a very big stressing point. I just wanted to talk about it because I think it's important to understand.

    [30:05 - 30:22] If I was to build tiny house from scratch right now and if semantic HTML was a priority, if accessibility was a priority, and if React testing libraries or library that I did want to use, I would potentially consider another design framework that AdDesign. Something to think about, not a big deal.

    [30:23 - 30:30] Maybe actually start to write our tests. We're still going to write them and I might mention a few instances where I saw some difficulty.

    [30:31 - 30:33] Okay. I think at this moment this concludes the lesson.

    [30:34 - 30:35] I'll see you in the next lesson.