So welcome everyone. This is Designing Effective Tests with
react testing Library. We're going to be together for about three hours working on this. I'm going to be sharing some information slides. I'm going to be doing some live coding and giving you some exercises to work through as well. So again, as I said just a minute ago in the lead up, codingitwrong.com slash workslot, workshops slash RTL Berlin is the web page for the workshop. It's down in the footer of all the slides. So as we go along, you'll be able to see it if you need to pull it back up. There's information about everything we're going through, links, ways to stay in touch and the link to download the exercises repository. So a bit about myself. My name is Josh Justice. I go by codingitwrong online. And as I always say, at the end of our time together, you can decide if you think that I'm coding it wrong. It's up to you. So I've gotten a chance to share some
react and
react Native
testing content a few different times recently. I got to give workshops similar to this one at
react advanced London this year. Really appreciate the organizers for giving me a chance to connect with you all here and there. Chain
react, last time it was held, a
react Native US conference, I got to do a workshop on
react Native
testing and I'm doing the same one again this next year in 2023 when Chain
react is finally back. I give a talk on test-driven development at
react Native EU as well. So
react,
react Native Circles, I move around here and
testing, in case you can't tell, is really my passion. It's really transformed my software development experience and so I like to equip others with helpful things about
testing as well. So social media. Social media is a little strange these days, isn't it, the last couple of weeks? Oh, okay, so that crashed again. My apps are also strange, so we're just going to do this in a simple way. So I'm not on Twitter. I haven't been on Twitter for a while now, over a year. But if some more people are using Mastodon, if you've tried Mastodon or you want to check it out as far as this other social network, I'm coding it wrong at
tdd.social, but you can find me there. I'm linked there from my page as well. So good luck as we all navigate social media together in the weeks and months to come. I've actually written a book about
react testing as well. It's on test-driven development and so we're going to be talking about
testing in general today. And if you like it, if you find what I have to say helpful, test-driven development is a way to approach
testing that actually really aligns very well with the philosophy of
testing we're going to talk about today. And so you can check it out. The intro chapters are actually free available. You can download, so you can check that out. I have a $10 off code for folks in this workshop. You can just use the link here. And the book is also available on Amazon paperback if you prefer paperback. But yeah, feel free to check it out if you would like. I work at a consultancy called Test Double. I didn't choose it just because
testing is in the name, but that did help. We're a company that improves the world's software. We feel like software is broken in general at large. Maybe you feel that way too. And we want to try to help fix it. So if we could help you by joining your team, embedding some developers with you, me or someone else, similar views to me or different views from me, we'd love to join and help you out. We work on
react,
react Native, many other web technologies. So you can go to testdouble.com slash agency to find out more about us. We also have a newsletter. Again, not knowing where social media is going. And we have had a newsletter. And so email addresses will probably keep existing. And so if you'd like to hear more content about
react and
testing and other software development principles, feel free to sign up at testdouble.com slash newsletter. We take privacy and people's
data very seriously. So your email will not go anywhere bad. It's just going to get you this newsletter until if you decide to unsubscribe. So feel free to check it out. What we're going to cover together today is
react testing library, the user event library that's closely associated and used for user action, and Jest DOM, which is some Jest
testing tools that make our assertions a bit easier to write. So we are specifically focused on component tests with
react testing library, not so much end-to-end
testing. Afterwards, the workshop webpage is going to continue to be available indefinitely. So you can always pull that back up to learn more. So the conference has a Discord. And so let me pause to talk about that. As we go along, feel free to ask questions as we go. If you would like to unmute and ask a question, totally fine, totally welcome. If you'd like to talk in Zoom chat, you can ask questions in there. But I do have Discord chat pulled up in Git Nation's Discord for
react Day Berlin. And that's a great place. That's maybe the preferred place to ask over Zoom chat, because that way those questions are available for people watching the workshop later, whereas the Zoom ones will not be available. So either one is fine. But if you are logged into Discord, asking in the workshop thread there could be great. And so after the conference, I'm going to continue to be in the conference's Discord, so you can reach me there if you like. I have my own Discord
community as well, folks that like to talk about
react and
react Native and
testing and test-driven development in those areas. So the link is on the workshop webpage. You can check that out if you like. There's a number of folks in there helping each other out on
testing things and edge cases that I haven't run across yet. And so it's a fun place to be. Something else I want to say is, I don't know, you can decide how opinionated I am in this workshop. I have opinions, fairly strong at times, on
testing. But I really want to make sure that I communicate. You need to decide for yourself. You don't have to agree with me. That's totally fine. If I get you thinking about something and you decide that you think differently on it, but it's prompted some thought, that's totally a win in my book. I really deeply believe that people and teams and products and organizations are different. And so your needs for
testing may be different than what I'm describing here. So I hope it's helpful raw material for you to evaluate. But please feel free to welcome to make your own decisions and have a learning mindset. All right. With that, we're ready to jump in. And as we go, we're going to take, because we're stopping three times for exercises over the course of the workshop, that will be a natural time that you can take a break as well if you need something to drink or a snack or something like that. For the second one, we'll take a bit of a longer time just to make sure people have time to take a break. But we'll be going through slides, me demo and live coding and these exercises together. All right. So with that, let's get started. Starting out first talking about just rendering basics when it comes to
testing. One other thing I should say in introduction is if you have, this workshop assumes you're familiar with
react that have written software in
react before. You don't need to be familiar with
react testing library. So if you've never written a component test, you'll have information here to equip you with the basics of how to do it. If you have written with
react testing library as well, I'm also sharing a philosophy of how to think about and how to structure tests. And so if you know how to do it technically, but sometimes wonder what tests to write or have kind of challenges with your tests, maybe that will be helpful. And third, maybe you already have your views on how you like to write tests and you feel like it's going smoothly. But maybe some of the concepts that I share help you to give you words and languages and ideas for you to have conversations with others as you work with your team to decide on
testing approach or maybe level up newer developers together. So whichever of those buckets you fall into, I think you'll find helpful information from our time together here. What is a component test is where we start out.
testing terminology, there are so many different terms. If you want to start an argument, just ask some developers what a unit is in a unit test and watch the fireworks. So I want to define the term of what we're talking about today. The term component test is a bit more concrete because in
react, we have components. And so the component test is a test of that.
react testing library is the library we're going to use for component
testing today. So if you, in your test, if you're rendering a component, you're specifying a component, usually with JSX in
react, that's a component test, most likely.
react testing library is probably the highest adoption as far as
react testing libraries, component
testing libraries. There's some other alternatives.
cypress has recently released component
testing as well, and it actually has some really great trade-offs. I love
cypress, and so I definitely encourage checking that out as well. And interestingly, even if
tooling changes over the years or you're using Enzyme or
cypress for component
testing, the philosophy of
testing we're going to talk about today applies in many other
frameworks as well. But
react testing library is specifically what we're going to be using. I see Papa asked, what's the channel for questions? Let me look into that in Discord here. There is an RDB Workshops channel, and in there, there's a Designing Effective Tests thread. And so that would be the Discord thread that you could ask questions on. And if you're having trouble finding it, you can ask here in the Zoom chat, and someone from the conference can help track that down for you, or you all can help each other, which is greatly appreciated. All right, so
react testing library. Many of you are probably familiar. The
npm package for it is at
testing library slash
react. The website is
testing-library.com slash
react. And
testing library is a suite of
testing tools. There's some for just the web in general. There's some for many other different
frameworks,
angular,
svelte, things like that. But
react testing library, as
react has a high level of adoption,
react testing library does as well. So let's talk about what to test. I've got a component. I want to test it. I or management thinks that
testing it would be a good idea. And so what can I test? Years ago, when I got started in
react, a lot of the questions were around, okay, I've got methods on a class component. Can I call those methods? Or I've got functions that run when event handlers. Can I call those functions? There's state. I want to see this code changes the state of the component. So should I check the state values to make sure the state value changed? These were common practice in
react component
testing years ago.
react hooks have helped prevent that because they were designed to not allow those kind of things. And for good reason.
react testing library helps with a different approach of component
testing as well. Dan Abramov, a very well-known
react core team member, had a great tweet in 2019 that I thought was very helpful. So I've hung onto it. He said, we don't encourage reading implementation details like state variables themselves. Instead, test observable behavior, what your component renders or does. This is a great summary of the approach of
testing that we're going to spend the three hours talking about. I found another person, another author that's talked about this in another way that helps us kind of wrap our minds around it, I think. He used the term
testing the contract. Maybe there's others, but I ran across this from Ed Yarborough from the book
testing vue.js applications. So this is interesting. Like in
vue.js, another front-end framework, he described this approach to
testing that totally applies to
react. And I would imagine every front-end framework as well. So in this book, Ed says, a component contract is the agreement between a component and the rest of the application. He says further, other components can assume the component will fulfill its contractual agreement and produce the agreed output if it's provided the correct input. So things happen inside the component. Maybe there's state, maybe you're using one of the one million
react state management libraries, who knows? Maybe it's a class component. Maybe it's a functional component with hooks, like we don't know. But what the test and the rest of the app cares about is that component has some inputs and some outputs. If I give it the right inputs, will the right outputs come out? And if we build a test suite where each of our components or many of our components are tested with
testing the contract, there's a lot of benefits from that. It gets us the most important things. It also frees us up so that we can refactor the component. We can make changes to the internal implementation details and the contract remains the same. So I found that this idea of
testing the contract leads to the lowest effort and the highest value tests because much more often, the test will support you in making changes and the test won't just be extra work to write whenever you've already changed the production code as well. So let's dig into what it looks like to test the contract of a component. First of all, I'll throw the question out and I'm going to make this one interactive if you all would like to answer in the chat in Zoom or in Discord. When you think about component inputs and outputs, what are some of the kinds of input and outputs that components have? So please share your answers. Any answers are welcome in the chat. Take a minute to do that. What are some of the kinds of inputs and outputs that components have? Nan says input props. Ludwig does as well. Absolutely. That's one of the most visible inputs to a component. Other components. Arohi, I'm sorry, I'm probably mispronouncing. Yes, that's a great point. Compass says output. I think they're interpreting that to mean output is virtual DOM. Zekers says output JSX nodes. Yep, I would agree. Jan says refs, context, output
react node. This is all really great. It totally makes sense. I'm checking over in Discord here. Inputs, props, outputs, virtual DOM and events. Yes, that's a great point. We're going to get to events as a key part of what we're doing. These are all great insights. I would totally agree with these. So let's take a look at the way that I've sort of sliced it and organized it for our walk through the workshop today. In
testing the contract, I would summarize it and this maybe not, I'm not processing live. Maybe there's things that you all have said that I've missed here, but this is just the way that I slice some of the main inputs and outputs of components. Inputs are props and user interaction events. So when somebody clicks or types, that is input. When they're typing in text, kind of clearly that's input, but even just a click or a scroll is input. Outputs can be rendered UI and also calls to external functions. That one can get controversial when it comes to implementation details. So we'll get into that and talk about that together. So this list of inputs and outputs is going to form the structure of what we work on together. And let's go ahead and get started. Rendered UI is what we're going to start with, even though it's not at the top of the list here, but in terms of examples, this is going to be the most obvious. Output, rendered UI. And so let's go to the code. Now we're going to switch to a portion where I'm going to do some live coding. One important note, this is a different code repository than the exercises repo. When you all do some exercises, there's some similar code in there, but it's not the same. So just know that when you see something on the screen here, you won't see it exactly the same in the code that you pull down locally. All right. So we're starting with just the very basics with a hello world component. It does have some images as well, an image and an
svg. I have to show this photo because it's just great. It'll just cheer you up. I found this in stock photography. Look at that squirrel. I can't zoom in very much, but that's a happy greeting squirrel. So hello, welcome. All right. We have a hello world component. I don't know if I would necessarily write a component test for this because it's so straightforward, but this serves for a great illustrative purpose of if we wanted to test this component or component with similar contents, how would we do it? So let's jump in together. First of all, about all the risks, let's ignore the image and the
svg for a second. Let's talk about the text. So what we can assert for that is that the text, the hello world message is shown. So let's go ahead and write a test to do that. So in our spec file, we are using Jest as a test runner, very, very common in the
react world. It works great for my purposes, so I feel no need to look into anything else. And Jest and
react testing library work together. Jest is the test runner.
react testing library provides functions for rendering and checking on things and interacting. So in my test, I like to have one describe block for the component that I'm
testing. And in here, I'm going to add a test to say it displays the welcome message. I really like the
api of Jest where you use a string to describe a test because it really encourages you not to be overly terse, but to write something in English that's describing the expected output for the input of the component. It helps you to really describe the functionality that's needed. So to start out, there is a render function. It's the one that comes from
testing library
react. So we get that. And then we can just type in the JSX just like we would type in in our production code to render out the hello component. So we save, the test runs, and it passes. So what happens so far is we've just rendered the component and we've confirmed that it doesn't blow up. The component does exist. It is a valid component, doesn't throw exceptions or anything like that. But what else can we do? We can assert on the text that's shown. And the way we can do this for the last, I guess, couple of years now is the screen
api. So we can say screen, which comes from
react testing library as well. This just gives us these functions that allow us to access what's rendered out. And in this case, we're going to do, notice that my language server has given us a list of many, many, many functions that are available on screen. We're going to do get by text to start out. And we're going to say hello world. So this is going to find the element that contains the text hello world, if it's there. And we'll talk more about different versions of these functions and how they act differently. Once we've gotten this element, we can do an expectation on it. And if you've ever done any JS, you can wrap something in expect to do a check and assertion and expectation. And what we can do here, we'll do to be visible. So this confirms not only that this element exists, but it's actually visible to the user as well. It's not display none. It's not opacity zero. I think it might even check if it's within the browser window as well. Maybe not. I think it checks that the size is not zero as well. So let's save and see. The test passes. So something that you'll see, this will be a theme as we go through here, is a good idea in
testing when something passes from the start, it's a good idea to make sure that it's not a false positive. Make sure that the test actually fails if what you're asserting for is not working. And because it's easy to end up with tests that just always pass, especially when we get into things that are asynchronous, it's very easy to make a mistake and find out, oh, yeah, my test says that everything was working, but actually it's broken. So it's a good idea to go and break your production code to make sure it fails. We can change the text here to make sure that the test fails when it's not found. It does. And so now we can scroll up. It says unable to find an element with the text hello world. There's some helpful notes there. It actually outputs all of the DOM, which is helpful when it's a small component. It's maybe a little less helpful when it's very large, but this does help you see what's going on. And we can see that the greeting message is nonsense here. It also shows you the line of the test that the failure happens on. So now we can go back and fix it and make sure the test passes. Another good reason to see the test fail is to see the failure message. Sometimes you can adjust the information that's given to it to make the failure message a bit more helpful for you or somebody else in the future if the functionality breaks. Jan says, the node scripts start building tests don't work on Windows because of how they set environment variables. Yeah, cross-env. Cool. Jan, thanks for that. Yeah, I unfortunately do not give as much attention to Windows as I should. So thank you for posting that. And maybe that's helpful to other folks as well. Maybe during the first exercise, I'll push that change up. Jan, if you would like to make a pull request, if you have time against that exercise repo, that could help me out, but I'll try to get that change in during the first exercise as well. All right, so let's continue on. So by default, the getByText does not match substrings. So if I just want to check that hello is shown when I say it, it says unable to find it. We can get around that if we do want to match a substring by using regular expressions. You may have run across those in
javascript before, but putting it in slashes means in the string that I'm in, search for the text hello. And there's all kinds of many, many, many features of regular expressions that let you search for things more sophisticated. But if we make this a regular expression, this says, yes, I found a text that matches this matching pattern, this regular expression. So now, matching things that aren't text. So in our apps, we have a lot of visuals as well, images, SVGs, and things like that. So this gets us something really interesting and helpful about
react testing library.
react testing library is focused on
testing the app the way a user interacts with it. And it turns out there's some helpful
accessibility practices for there. When we're writing in code, we can't very easily get something by, you know, finding, I don't know, maybe if we've got AI in here, it could say, make sure there's an image that looks like a squirrel and looks happy, and we could match on that. But we don't have that currently, maybe next week. So for now, we need to write text in our test to match on things. And so this means we need something text related on the image that we're going to be able to match against. And so there are abilities to do that for
accessibility, like for images, for SVGs, you can provide a text description of those. And that doesn't only help your test, it also helps people who are blind or people who have limited visibility and are using screen readers technology to be able to interact with your app. So there's this really beneficial thing that comes out of this where by writing a test, you're now using your app in a second way, other than just interact with it directly. And if you're somebody who doesn't have visual impairments, your tests don't see the code that helps to ensure that your app is accessible. If you write your test the way the
react testing library recommends. In this case, I actually have a linter that's failing already. JSX A11Y is saying image must have an alt prop meaningful text or things like that. And so if we fix this, this is going to give us something to assert against. So we can say alt squirrel waving. That's a description of the image. And now we can query against this alt text in our test here. So we can say it displays the squirrel photo. We can copy and paste the render again. And we can say expect screen get by alt text squirrel waving to be visible. Save and it passes. Again, don't trust it. Let's see it fail to make sure I've gotten it right. By change the alt text, the test fails. And it says it couldn't find an element with the alt text squirrel waving. But in our output DOM, we can see the alt text. This brings up an interesting point about tests where when we're searching based on text, this is kind of coupling to text that is shown. This kind of requires your test environment to be set up. If you're using an
internationalization library, your test environment needs to be able to be set up to be able to output text for a given locale. And this also exposes you to if your copy changes, if the
design or UI UX folks come in and say, oh, we want to rephrase how that's done, your test might fail. So there's a trade off there. What the
react testing library
community has found in their view is that you're better off coupling to text that is shown because of the realism benefits you get. And so the downside, like they would recommend that when you're writing these component tests that are closely connected to the components, if you're changing the text, like it's probably that test that's going to fail, you can change the, it's not a lot of work or expense or complexity or cost to update the test to match that text. And you get enough of a benefit for realism to be able to make that worth it. Yeah. Oh, this is a great question. Is there a way to increase the max size of
html output when a test fails? My components are too large. Oftentimes that's, I have that question as well. And so I'm looking forward to you can use screen
debug. Someone says and pass in a maximum output size. That's super helpful. I learned something today. Thank you for sharing that knowledge that that is going to help me on my big material UI
data grid component that I'm working on right now. So great tip. Everyone please feel free to continue to ask questions and share tips with one another as we go along. I see one note about
accessibility. I do wish the
react testing library had to get by selector functionality to deal with third party components. Yes, that's a great point. When you're working with third party components, where you don't control the elements underneath it, things can get complex in a lot of ways for
testing purposes. All right. Let's talk about the
svg as well. SVGs can be loaded in, in different ways. I'm using create
react app here. And in create
react app by default, you can import an
svg file as a
react component. You can give it a name and render it out like this. And so in this case, your
svg setup might be different. You may need to look into something else. But in this case, SVGs have a title prop. And we can add a title prop to test for the presence of this waving hand
svg. Oh, we can't visualize it there. There's the
svg. Can you visualize it? This time let's write the test first. You know, this is test driven development in a sense. You can write the test and watch it fail before you get it passing. Let's just try that just to see what that's like. We can say it displays a waving hand icon and render out the hello component. And then we can say, expect screen. In this case, there's a get by title. There may be, there are so many different functions as a part of
react testing library. So some of you might be familiar with other ways that you could create these things. And so, you know, those are very welcome as well. There's not just one way to do it. Clearly, when you look at all those many, many, many different functions. But I found that get by title works. So we can say get by title waving hand to be visible. I want a waving hand icon to be visible. When I save this now, the test fails. And that's as we expect because we've written the test before we've gotten the functionality working. Unable to find an element with the title waving hand when we see an
svg there with no title added. Now to get this to pass, we can just add a title prop to the
svg component waving hand. Let's save and the test passes. So this actually shows one of the reasons that test-driven development can be helpful. If you're taking the approach of, I want to make sure I see my test fail because I want to make sure it's correctly catching my functionality. It's actually more work to write the test passing, break the production code, and then write the test, fix the production code to get the test passing again. It can be easier to first see it fail because you wrote the test first before you got the component working. I'm not dogmatic about test-driven development. I use it when it's helpful, when it's useful. It can certainly be challenging with UI work like
react work, but it's something you can reach for sometimes. But that's not the main focus of our time together today. Just wanted to take the opportunity to show that. All right. With that, we've done some basic
testing of rendering to move on from there. Oh, the slides. All right. So let's talk a bit higher level about what we saw. In
react testing library, there are these getter functions on screen. Some of them are getByText, getByAltText, getByTitle, and getByLabelText, which is useful for
forms, which we're going to see a little bit later. So just to show you those, to summarize them, if you want to get text, you getByText, and there it works. And so if you have any text rendered out, regardless of the element that is contained by, getByText will return it for you. So here's an example of the form element just to give you a preview of what's coming later. If you do getByLabelText, you can enter the label that is shown, and it'll give you the input rather than the label. Here I show the example of where in
html I'm wrapping the input inside of the label that's containing it, but this works just as well if you use the HTML4 and the ID attributes to have separate labels and inputs connected to each other. Again, there's this
react testing library philosophy of
testing like a user. A user knows what input to type in because they see or have a screen reader tell them that enter your name is the label for it. And so our tests are accessing that input in the same way instead of like a test ID or some kind of lower level code thing. All text. Images have all text. Hopefully you have JSX A11Y that's warning you in your editor if you're missing all text for an image. And so if you have that present there, you can pull it up. Vicky says, adding title on
svg does not really make sense, I think. Cool. Thank you for that link. I'll check that out. If we can find another way that allows SVGs to be pulled up in the tests, then that's great. Ideally, it's an attribute that's useful both for production use by users as well as by a test. So I'll check out that link that you shared and others feel free to add recommendations in here as well.
svg title. Again, summarizing this, I found that this works. Title is a document attribute for SVGs and there is a getter to be able to pull that up. But yeah, if that article is shared, it might be another way to pull it up. The key is that with the
svg technology you're using, that the attribute you add is in fact outputted on the
svg. And maybe REI label is. Not sure. This could be something you could feel free to use the exercise time to play around with this if you would like and see what works in this tool set. So which query should I use? We've seen so many different getters. And so the question is, which should you choose? There's test IDs. That's an option. Should you use that? First of all and foremost, in
react testing library, they describe a philosophy of how to think about queries and how to think about your tests. And they say your test should resemble how users interact with your code, component or page, as much as possible. That's what I've been kind of trying to describe as we go along. And so this leads to a certain priority order. In their list of priority order, they put get by role at the very top. We'll talk about what that is in just a second. Then after that, get by text, display value, placeholder text, and label text. And they describe the trade-offs, the pros and cons, but they try to organize them in a priority order. If you've used test IDs before, notice that that's not even included on this slide, and it's way down at the bottom on their list. A test ID is something code specific you put in your code for
testing. There's benefits to it, but the downside is just because your test can get something with a test ID, the user can't see that. So the user might not be able to find what they're looking for. It may not be accessible. There may not be a label or the correct
accessibility role. So preferring these queries recommended by
react testing library can help your app to be beneficial in other ways. So a note on get by role. It is the top recommendation for the query method to use. And we're going to be using it. I'm going to show an example of using get by role in the forum coming up a bit later. I do recommend using it. However, there is some learning that's needed. I think it's important learning. I think you should do that learning, and so I do highly recommend it. It requires learning about ARIA roles. So the project that I'm on right now, we're skewing towards using get by role, and I'm learning about column header. I'm learning about menu item, these different ARIA roles. And we're using the material UI library, which automatically applies many of these, which is great. It's very helpful. But there's a learning involved with that. And so for the sake of just basic introductions to
react testing library, I wanted to not always use get by role because I didn't want three hours. It's hard to teach
accessibility at the same time, and I'm still in the progress of learning as well. I will not say that I'm the best
accessibility person at all, very much in process. So my recommendation is check out get by role. You'll see the benefits of using it. It improves test readability as well that we'll see in our forum test coming up. Confirming presence. So now that we've gotten our elements, you've got, we have to, you know, confirming that they're present is one of the most common things you want to do. Is it there on the page? Really, the way I would recommend, and I think it's recommended to do that, is expect screen.getby whatever to be visible. So this makes sure that it exists, that it's there. But it also does additional visibility checks, like to make sure it's not display none, to make sure it's not opacity zero or height and width zero. And this, again, fits with that philosophy of
testing like a user. If something is present, but if it's display none, to the user, it might as well not be present. That's an implementation detail. And so expect to be visible, probably most closely aligned with what you're thinking about in terms of what your user needs. There are cases where, that's not the case, though, where you want to confirm, like it is display none, but you just want to confirm that it's there, that it's present. If that's the case, you can use expect screen.getby to be in the document. And that does some extra check-in as well to make sure that it's not a DOM node that's still present but got removed. That can be an issue in
testing in general. It makes sure it is a DOM node that's present in the document. Vicky says in the chat, if we just need any unique attribute value to find the element, I guess it could be ID of the title, or ARIA label by if you add those for
svg accessibility. Cool.
css tricks is a great resource. So I'll check out that link and I'll add this onto the workshop web page as well for reference. All right. So confirming absence. So now there's the opposite side. You want to make sure that something is not present on the page. So how can you do that? There's a bit of, it's similar, but there's a bit of a difference needed. So in addition to getter functions, there's analogous query functions as well. And they're all repeated all the way through. So for every get by, there's a query by as well. And the difference with query is if something is not, let me back up, with getters, with get by, if something is not found, the test immediately blows up. And we saw the errors that we saw where it says, this was not found. We saw the DOM or the JSX tree outputted there. I don't know which one of those it is, either way. And so it's a very helpful error message. But there's cases where we don't expect something to be found, where we need to confirm that something is not there. So query by is useful in this case because query by will return the element if it's found. And if it's not found, it will return null. And so your test is able to continue. You're able to make an assertion on that. And so the assertion you'll tend to want to do is expect screen.query by not to be in the document. Earlier and more recently in
react Native, you needed to check, and I think maybe still do in
react Native, you check if it is null. And yes, you could do that. You could check if query by returns null. But not to be in the document is a bit more expressive. It's a bit more readable. That's what matters. Maybe it changes to undefined in the future. I don't really care if it's null or undefined. I care that it's not an element that's in the document. And I think there's other checks in to be in the document as well that can be helpful. So this is just something that you need to note. If you're copying and pasting code to confirm that something's present and then absent, ideally for the presence, you'll say expect get by to be visible. And then when you're confirming that it's absent, maybe it's been removed, you say expect query by not to be in the document. So there's a bit of verbosity there. But using those APIs gives you the optimal assurance. Alexi asked one of the questions that has been my biggest question for years. Is there no reason to wrap get by and expect since they throw anyways? To be visible does some more checks. So yeah, this before to be visible, and actually in
react Native, there's a similar question. Like get by will already blow up. So should I wrap it in an expect? Ken C. Dodds, creator of
react testing library, has great resources on this as he often does. And let's see if I can briefly summarize it. So in addition to the to be visible check, which is other checks, it's also helpful in your test to wrap it with expect so that you can see, oh, I'm intending to use this as a check. If you just had the get by by itself, another developer or maybe you in the future might look at that and say, oh, is that leftover code? Is that accidentally copied and pasted? It doesn't look like they're checking, they're intending to confirm that something's the case. But when you wrap it with expect, it communicates to the reader of the test, I'm making an assertion, I'm confirming something here. That's the intent of what I'm doing. It did feel kind of weird like in
react Native when I wrap something in expect, but it's not actually adding any more functionality. But it is valuable to think about what your tests communicate. So they're helping the reader understand what it is you're trying to test. But in the case of the web, we have the to be visible matcher, which provides extra helpful checks as well. Again, I'll find that Ken C. Dodds article and include that on the workshop page. I'll share it in the chat if I find it during our first break as well. So that's the first part of
testing the contract. We've covered rendered UI,
testing rendered UI. We're going to move straight on to props because that's very similar. It doesn't take a lot more work to get into props. So let's do that. So let's go to the code. And after we do props very briefly here, we'll have our first coding exercise. So let's change our component. Let's refactor it to take in a name that's customizable. So we're going to have a name prop here. We're going to pass in world by default so that it keeps working even if you don't pass a name prop. And then we're just going to output hello name down here. Let's save changes. Notice that our tests all still pass. So our test that checks for displaying the welcome message. Now, even though name can be configurable, when we don't pass it, the test still passes. Although I guess I'm not even checking for the name here. So we could say, let's go back to hello world. That still passes as well. So we've been able to refactor and change some of the internals of our component with our test confirming we haven't broken something that was working previously. Is there a point to using get at all? If query plus expect fails anyway, something is not found. Throwing feels like overkill. That's also a question that I've asked. So that's a great question. You could. Your tests would work if you use query all the time. And for a while, I did do that. Query for when it's there, query for when it's not there. You do get a bit more expressive errors in the case of get by. Because when you're using query, you just get a null back. And so your tests tend to... Actually, let's show it. Let's show it. Let's say get by text and we have a failure. So here we say, I'm able to find an element with the text, hello, blah, blah, blah. And we get the DOM output. So let's try changing that to query by. So now the output we get is receive must be an
html element or an
svg element, but received has value null. So first of all, the error output is not as readable. I mean, you can see the line of code there, but you have to kind of... Your brain kind of has to parse this. And also you don't automatically get the helpful output of the DOM that was present. And the reason why is kind of the way the
javascript is executed. When
react testing library is running this, it just knows that you're trying to query by this and it should return null if something's needed. So then when Jest runs and it says, expect null to be visible, it doesn't know that you ran a query by. It just says, oh, you've got a null here. And so that's what it outputs. And so maybe folks that know the internals of these libraries could find a way to make query by fail in just the same way. But basically by having get by fail early, you're getting currently with the way the code is currently implemented, the libraries are implemented, you're getting this more helpful error output. You will still get the right failures and successes using query by, but think about that philosophy that I said of help yourself and help future developers and future you by setting up your test to have helpful failure messages. So that's the reason why
react testing library recommends using get by for positive finds. Oh, yeah. And Jan says, if you want to click an element, you have to do a null check with query by. With get by, you can use it directly in fire event. Cool. That's a great point as well. And we'll see that when we get to clicking shortly. Say, let's fix this. So back to where we were. We refactored our component to add a prop. And we saw that our existing tests continue to pass. But we do want to add a test for this new functionality as well. And we do have a passed in name. So let's add that in. It renders the passed in name. This is important when it comes to thoroughly covering components by tests. When you have a conditional, even a conditional of supplied value versus default, it's important to test both sides of that to get full coverage. So you know that both sides work. If we didn't have this test, we wouldn't know if the component might blow up if we didn't pass in a name prompt. You need to make the choice in your own code whether you want to get full test coverage of all branches. There's a lot of pros and cons there. But there's a benefit to getting more coverage in a case like this. Let's say hello name Josh. So what do we expect? We expect that what we're going to see is hello Josh to be visible. Save, and it passes. And again, let's not trust it. Let's see it broken. This is a trivial case. But I've been burned enough by false positives that I kind of almost instinctively like let's see it fail. So it fails when I'm not outputting the name, but when I output the name, the test passes. So we're covering that prompt. And I'm sure there's more cases that we could get into, but that's basically what I have to say about props in your tests. So let's look at that. Yeah. The end. You can pass in a prop. One of the nice things about
react testing library is that the JSX syntax for rendering is exactly like the syntax you use in your production code. It really helps with test readability. That's not the case for all front end libraries, but it works out well for
react. So with that, we have our first exercise. And again, we're going to kind of combine the break with that as well. So
react testing exercises. I might have gotten the name of that repo wrong. Let me check that out to see to make sure. I'm going to pull up workshops. Exercise repo. This is the workshop webpage. Oh, yeah, that's right.
react testing exercises. No, that's not right. That's not right. Sorry, everyone. I think, what have I done? Too many workshops. RTL exercises. Okay. I think the other one might work as well because they're very similar. But let me put this in. Sorry about that. Jan probably did the pull request to the other one. This is the correct exercise. Sorry about that. I'm going to get that updated on the webpage. RTL exercises for designing effective tests. And let me show you what you'll see when you pull that down. All right. Let me pull this up over here. So again, as a reminder, this is a different repository from the sandbox repository that I was demoing out of because this is really just focused on what you need for your exercises. There's a readme here. Windows will need that edition that Jan says. So thank you for that. As it's described in here, there's an exercises folder with a writeup, a markdown file for each of the three exercises. Right now, we're doing exercise one. And I know you can read, but I'm just going to talk through what we've seen here a bit. This is a kind of a cohesive app where we're managing a list of movies. And we have a movie list. We have a movie row within the list and a new movie form. You should be able to get this running locally and kind of see it working. But we're
testing one component at a time as we go through. And to try out
testing rendering, we're going to test movie. I think I'm on my solution branch here already. So movie row is very simple. I've tried to keep it as simple as possible to not distract with third party UI libraries or anything like that. But this is a row where we output the title of a movie. And if the server gives us a Boolean property added today, if the movie was added today or if it was not, it's true or false based on whether it was added today. If it's added today, we use this new
svg icon to show. And I have a title prop there, which maybe there's better ways to do that. So the goal of the exercise is let's write a test for movie row that covers all of the functionality, all of the inputs and outputs for this row in isolation from the rest of the application. So that's really the goal. There's a movie row spec file. I want to open this up because this has my solution on there. But yeah, there's some tips on here on how to
design your tests. There's some notes on here, a link to the
testing library docs with information about those APIs that we've seen together. All right. I want to show the movie row test that I wrote and kind of explain my reasoning. And then any input from you all is welcome, certainly if you found other ways to approach the
svg here. So as I was thinking about the movie row component, and let me run this test, new tab with current profile. There we go. Gotcha. So for the movie row, I wanted to test that the name was being displayed. So I set up a movie object here. Note that actually in this test, I only provided one property on the movie. The others were not required, like the test didn't blow up when that wasn't there. I think coming back from the server, there's IDs, might be a created ad and things like that. But my test only required this minimal
data. And so that's what I've put in. If we were using
typescript, it may be more strict depending on how you have it set up. But one of the things I haven't used
typescript a whole lot, but one of my friends who uses
typescript, he actually recommended even when your application code is in
typescript, writing your test code in
javascript, because it allows you to be a bit looser. Like in production, you want to make sure that all the types match up. But in a test, it might feel like jumping through hoops to have to provide a full object when just an object, literal with one simple value can help. So take that suggestion for how you like. I'm sure there's a lot of folks that write their tests in
typescript as well. But the idea of minimal test
data to keep the test nice and readable worked. So I got by text the movie title, and I checked if it was visible. Then I added two different tests for rendering the icon when the movie was added today, and not rendering the icon when the movie was not added today. Yeah, so I check get by title added today to be visible. And then when it's not there, when added today is false, I checked that I do query by added today and make sure that it's not in the document. And again, these are the tests that if we find a different way to test for SVGs, we could totally do that. So another interesting thing, and this was a judgment call that I made, I really like, and you'll see this as we go through, to have small focus tests. Sometimes folks say one assertion per test, or you could say one expectation per test ingest. And that's what I'm doing here. Sometimes that's a little bit far though. Say you had a form with a first name and last name, would you have two different tests to make sure the first name and the last name were shown? It's kind of the same thing. And so in that case, one way I've heard it is to test one behavior per test. And so in this case, you know, rendering out the
data is kind of what happens here. But I had a conditional that I needed to test here. I needed to test when the icon was present and when the icon was absent. Now I could have put the title in both and checked for the title twice. That felt a little duplicative. I could have picked one and said, oh, I'll check for the title as well as checking for the new icon as well at the same time to get down to two tests instead of three. But really, I kind of liked this the way it worked out here. The fact that it's kind of different situations. This is kind of what's shown unconditionally. And so if there was like a movie director, I might put that in the same test here as well. But then there's here these two different conditions. It's two different situations you're in, whereas the first test doesn't care about whether it's added today or not. So in this case, I separated out into three different tests. There's pros and cons of that. But one of the big pros is test readability of being able to see. And I think the just approach of having these descriptive English test names guides you to this in terms of like, I want to be able to summarize in one brief sentence what the test is about. And it's kind of hard to say, you know, when added today, display the name and also display the icon. But actually, you always display the name. Like summarizing, it feels kind of awkward. And so I let, you know, what are things that are easy for me to summarize behaviors of the component and its contract that are easy to summarize. And I let that guide the test structure that I make. It doesn't hurt things if you group things in a different way. While I'm getting ready for, and I do want to keep us moving on, but while I'm getting ready for the next portion of our demos, any thoughts or feedback you all have, different approaches that you took to your own test for this exercise or things that you would suggest differently, feel free to put that in the chat. Lottie says, you can also add
data test ID to the
svg and use get by test ID. So yes, you totally can. I guess I should say I haven't directly tested that on
svg, but I trust your assertion there. Test ID is discouraged in
react testing Library in general. And so I'm not sure if title is bad to add to an
svg, it's probably better to use test ID than title. But if title has any benefits, maybe it's better to use that. Vicky says, why not to use
data test ID? Cool. Yeah. Alexia says, maybe it makes sense to test the absence of
svg badge when we don't pass the add today property at all. Totally. That's something that I think about a lot in my tests are. Are there cases, do I care here about truthy versus falsy versus null versus undefined and actually in
javascript objects, an undefined value is different from the key is not present at all. So depending on where your code is, you might care about those differences or you might not care about them. And it might make sense to cover all of them individually in your case, or it might make sense not to. These are some of the judgment calls that come into when you're writing to your test and just deciding how much detail you want to get into. Cool. Let's pull up the next slides. A few more notes about
testing rendering and props.
testing appearance. So
storybook is a tool you're likely familiar with. It's been around for a number of years now, and it allows you to see just a catalog of the components in your app, and you can very easily specify different arrangements of props. I actually saw a great talk by Michael Chan at
react advanced London talking about some more
advanced things you can use in
storybook, even connecting it to automated
testing, which is pretty cool. So when it comes to
testing visuals, we've talked about
testing the contractive component and
testing its behavior, and we've really focused on things that are testable in Jest. So that doesn't test the look and feel. Let me check out this graph. Like, how would you write a
react testing library test to make sure that the gradient is right and that the circles, I mean, you can maybe test that the circles are the right size, but that they're the right color, that it looks good, you know, that this text isn't too large or too small. And so rather than trying to shoehorn that into
react testing library, I find it better to use a tool like
storybook. Like, if you want to visualize all the different states of that component, bring it up visually. What about snapshot tests? So Jest snapshots is a feature of Jest, and I think I don't use them very often, so this syntax may be out of date, but at the time, this is how you would do it. When I first created this slide, you have some JSX, you use
react test render to render it out, you convert it to JSON, and then you say, you confirm that matches the snapshot. And something for folks that are new to
react might not be familiar with, the word snapshot doesn't mean it's an image of the visuals. It means taking this JSON here, it's a snapshot of that
data structure. So you're saying, does this component render out the same
data structure of JSX, JSONified, that it did before? And it automatically records the output and updates it later. So that's not a visual, that's
testing the output. And Ken C. Dodds has a blog post where he talks about Jest snapshots. He actually responds to something by Justin Searles, who's one of the co-founders of Test Double where I work. Justin talks about some downsides to the snapshots. I don't think I put this blog post on the workshop page, so I will add that in because it's a helpful one. Downsides to snapshots, the fact that it's not expressing the user's intent. How do I intend this component to render out? It's not clear here. And it's a very easy and very tempting thing for snapshots to just say, when it says, oh, something has changed in the snapshot, just to say, yes, it's fine. I ran a new case on a project where I was doing a refactoring and reorganized things and the component looked and worked exactly the same, but the snapshot changed because the internal
data structure of the JSX had changed. I think it was some key values or something like that. That I don't find valuable. That's not a useful test that's adding a lot of help. It is work when it fails. I do use just snapshots for something else. I have a command line tool that generates a README file. And so I use snapshots there to easily capture what does the README file look like. For
react components, I generally, and what Ken C. Dodds says is, in that blog post, don't do a snapshot of the whole component. Do a narrow snapshot of just a portion of it. And in fact, if you narrow it down all the way, what you're going to end up doing is not using snapshots at all. You're going to say, look for this text, look for this
svg, or look for something else. And that's the advice that I would follow. If you find that snapshots work for you, great, go for it. But I have found the behavioral
testing that we're doing here in this workshop to have higher value for me. And then using something like a
storybook to be able to easily visualize so that you and your designer and your product owner can see what things look like and use your human brain to check on the visuals to make sure that something doesn't look weird or off or non-optimal. Cool. I see a lot of notes in here. So just to stay on track, I'll let you all chat in the chat here because I want to make sure we get through everything in our three hours. And during our breaks, I'll try to catch up on the chat and interacting there. So number three, actions and mocks. This is our second segment.
testing the contract. So next we're going to talk about this other type of input, user interaction events, which is a type of input. So let's go to the code. So new message form. So this is just a very simple, just the most trivial form you can imagine probably in
react. It's like for a messaging app. You type in a message and you send it. So you have an input, got some nice label around it for
accessibility. We have a send button. And when we send it, there's an on send prop passed in here. It's just a function. Maybe it hooks you up to Redux. Maybe it hooks up to the larger application.
react query. Who knows? But we have a prop for on send passed in. Or if that's present, when we submit the form, that's called and the input text is sent to it. And then after that, we clear out the input text as well because the user, once they send one message, they don't want to have to send it repeatedly. Okay. Let me get my notes pulled up. There we go. So how can we test this form? Well, what are the behaviors? A way I would summarize it is when we click send, that's the situation we kind of care about after we type in and click send and we fill out the form. There's two things that happen. We clear out the input text and we call the on send function prop. So let's test one of those at a time. It's helpful for the workshop, but also I like
testing one behavior at a time because I like separating out the tests in that way. So first, let's do clearing the input text. So in our new message form test, I like to add a describe here to say when the form is filled out because you might also test the initial state before the form is filled out. So we're going to say here, it clears the message field. That's the one behavior. That's an output. It's a change to what shows up in the rendered JSX tree. So the first thing here, we're using this user event library, which is not new-ish, but there's a new version that's much better that improves even further on it. But user events is a library for interacting with your code, provided as a part of
testing library. So as a version 14, the
api to use is you get a user object from saying user event.setup. Then we're going to render our component, new message form. And then we're going to type. So let's start it just by typing. We do need an async test here because we're going to await user.type screen. And this time we're going to get by label text because we have a text field that has a label. That's very important. That's from the early days of the web. I remember the importance of labels for
accessibility. And the label is message. And what we're going to type in is hello world. This may be a bit of a different syntax that you're used to if you are still using fire events or if you're using an older version of user event. User events methods were inconsistent in the past as far as whether they were asynchronous. But now they are consistently asynchronous. So they always use an await. So that's great for consistency. And it's also helpful for other reasons we'll get into a bit later. So we're going to type in hello world. And then we're going to click. And that's await user.click screen. And this time I'm going to use get by role. Because this can be one of the easier roles to learn about. Button is a role. And it's important for buttons to have the role button so that screen readers can communicate to the person with visual impairments that it is a button that's interactable. And we say button name send. So this is nice because we're not just looking for text send. Maybe send is the heading of the area as well. But we specifically find a button with the title of send. As someone asked in the chat, if there's only one button, you could just get by button. And then you're fine. But this makes it more robust so that if a different button is added in the future, we make sure to get the send button. And we also confirm that the user can see send as far as something that's specified on there. So this is going to allow us to type and click send. The test passes. So nothing's blown up. This confirms that these are actually found. Let's go back to something someone said earlier as far as get versus query. Say I say label text message exclamation point exclamation point. We save. And that's not found to type in. And we say unable to find a label with the text of message. And we get the output here. If we did query there, then so this is actually even a worse error message here. Cannot read properties of null. Reading disabled. So this is not very expressive at all. Why is there a null? What have I done wrong? It's not very clear. But by using get by label text, we tell
react testing library, I expect there to be an element there. And it gives us a nice clear error message. Unable to find a label with the text of message. These kind of things are very helpful when you're going into tests that were written months and years ago and something has failed and you want to understand why. So readability error message is helpful. It's worth the complexity of how you remember get versus query, I think, a lot of time. All right. So we need to make an assertion here. So this is all enough to do filling out the form. But now how do we confirm that the message field is cleared? We can say here, expect screen get by label text again to get the message input. And we can say to have value empty string. This is to say we're confirming that that text input is empty. It has been cleared out. And it succeeds. By now, you should know we don't trust it. Don't trust it when it's green. We need to see it fail. So let's comment out the production code that clears the input text. When we comment it out, we see expected the element to have value. And because it's an empty string, it's not so easy to see in there. But if you're used to this, you can see, oh, expected to have the empty value. But instead, we received hello world. So this confirms that the input is actually cleared out. We save it. And then it passes. So something this, yeah, I don't think this is in the slides. Let me describe it now. When you see this, depending on your thinking on tests, you might say, hang on a second, I'm checking the value of this element. Value is a prop. That's a prop that's passed to the input. Is this
testing what the user sees or is this an implementation detail? The user doesn't care that you have an input
html tag and you pass a certain value to it. The user cares about what the text is that's displayed. So is this
testing implementation detail? And what I would say is, I haven't figured out a great way to summarize this, but basically, we are
testing that the text is cleared out. We're
testing that the user sees an empty input field. But to have value is the name of the matcher that does that. If you don't like the name, you can make a custom matcher. You can do that in Jest that says to be an empty text field. And you can describe that. And that maybe is a bit more expressive. But as long as we're checking something that is visible to the user, if the method we're using is to have value and that's checking the value prop on there, that's something that's stable. I've been a web developer since maybe 1994 in high school, middle school. And the value attribute of inputs has not changed in that time. So I think it's stable. I think we're going to be okay. There is nuance in
testing and there are trade-offs. The idea of don't test implementation details, that's a good principle to go by. But it requires some thought. What is and what isn't an implementation detail? And we're going to see that a bit later when it comes to mock functions. All right. Let's go on from here. So to have value is provided by the Jest DOM library. And we'll see more about that in the slides. Cool. Let's go over to the slides. So here is just, and I'm going to go into play mode here so I can see some highlighted fields. This is the version of this test that I wrote that is maybe the same or maybe there's a typo difference. But just to highlight the lines to see again what we saw before, interacting with the component, user input, user interaction is an input into the component. And so we can do this with the user event library by calling user.type and user.click. And there's some others as well. We'll see a bit later. So in user event version 14, this is the
api. This is a change from user event 13. So you call user event.setup to get a user object that's specific to the scenario that you're in. And then you call user.click, user.type. There's user.clear, which will clear out a text field as though a user had deleted the text out, which can often be helpful. There's also select options for when you have a select dropdown and you need to choose an option or multiple options from it in the case of a multi-select, which is not too common anymore. One quick note, a lot of us are not using Create
react app anymore.
react Core team is moving towards recommending other
tooling. But if you are using Create
react app, as I am for this demo repo, it installs version 13 by default. It hasn't been updated to user event 14. But I really, really strongly recommend updating to user event 14. And maybe in your own projects, you pull in user event yourself and you haven't updated. Version 14 has a lot of robustness. Just the fact that everything's asynchronous helps a lot with some of the asynchronous issues in
testing. In my book, I had a whole chapter working around like, oh, we're getting the act warning in
react and how do we get around it? And asynchronous complex and here's some things and there's like trade-offs for different ways to go about it. But when I updated the book to a user event 14, everything just worked. It was just simple because, and this really gets to the
testing library philosophy, because user event acts more like a user. They do focus events when you're clicking on a text field. I forget the name of the value change events. Focus off, things like that. It's more realistic and it's realistic that users take some time to interact with your form. And so things just fall out and your tests end up being simpler and more reliable as a result. So I really, if you haven't updated already, I highly recommend updating the user event 14. You'll see a lot of benefits from it. And then again, as far as checking, asserting the value in the text field, we pull up the text field by label and we call it to have value here. To have value is provided by Jest DOM. There's other ways to do it to get directly to the props of an element that you're given, but just using this function here by a third-party library, a well-trusted library saying, hey, to have value is something you could check. That's a bit safer than manually digging into the details of props. Maybe
react or
react testing library internal
data structures will change in the future, but as long as Jest DOM supports to have value, it's going to work reliably. So that's user interaction events. And the basics of it, there's of course much more, but that's an introduction to it. So let's get our first steps into calls to external functions. And there's actually multiple ways to do this that is going to take us through the rest of our time together. But just to get this form
testing working, like we need to do some
testing of an external function. We want to see, in our case, there was a function prop. And so we need to see how it was called. So really, when it comes to external functions outside of a component, there's a few different types of external function that are maybe tested in different ways. There's function props. There's function from hooks, which kind of opens a can of worms. Hooks, can of worms, that's funny. Because there's different ways that that can work. So we'll defer that and come to that a bit later. There's also function statically imported from
modules. So you do an import from another module to get a function that you call. We'll see that separately. But right now, we're just going to take a look at function props, because that's what we have in our form that we saw a minute ago, that on-sand prop. So for
testing function props, I recommend using Jest mock functions. So here's the page. These slides are available on the page there, but this will be linked in the exercise reading as well. But if you just go to jestjs.io and look for mock functions, you'll see the docs for it. A mock function is any kind of stand-in for an external function in a test. Some of the things you can do with it are you can check how it was called, and you can set up return values that are helpful for
testing your scenario. To create a new mock function, you do this. You call jest.fn, which is nice and concise. It's maybe not the most descriptive, but it's a jest function. It's not another function. It's a jest function, which is a mock function. I always recommend calling this mock name to give it a name, and we'll see why a bit later in the example code. It helps with test failure readability. What can you do with a mock function? Well, you can call these matchers on it. You can confirm that a mock was called. You can confirm that it was called with specific parameters, and even if you care that it was called with something, you don't know exactly what the value is. Jest has the APIs expect any string. This can be helpful if you have a UUID that's being generated. It's like, you know what? I don't care what exact ID is generated in here. Just confirm that a string is passed in here, and that's good enough. But it is good when you do know the exact value. It's good to test for it. That tends to lead to more robust, accurate, understandable tests. Let's jump into the code to see an example of using jest functions in action to confirm that the output happens here, that this function we passed in is called back out. Again, I am going to thank you for the chat, everyone. I am going to check during our next exercise section. I'm going to check the chat notes there. Maybe before right at the start, when we start that exercise, I'll check there to get back to any questions you have. So, we tested one scenario here. We tested that when you fill out the form, it clears the message field. But that's kind of secondary. Really, the most important thing we want to check is that it calls the onSend prop with the inputted text. This is not how I wrote this in my notes, but that seems like a more descriptive test name to me. So, for now, to start out, let's copy and paste the setup. And actually, after this, we'll show how to kind of remove that duplication. We still want to get—oh, we need an async function here. We still want to get user events set up. We still want to render out the message form. We still want to type in and click the send button. But now, what do we want to check? Well, we need to do something a bit differently. To call the onSend prop, we need to pass an onSend prop. So, let's set that up. We'll call it send handler, jest.fn to create a mock function. And we'll give it a name. And we'll see—we'll remove this later to see the difference that makes. And then we pass it in. The onSend prop, give it the send handler. So, when that's provided, and we click the send button, we want to confirm the send handler to have been called. And in our case, we specifically care about the value. So, let's pull that out into a variable here, const message text of the world. This way, we can type in the message text here, and we can confirm that that same message text is passed to send handler. I could have just copied and pasted the string, and sometimes that is more beneficial. But in this case, I think I kind of prefer having a variable. So, you can see, hey, the same thing that's inputted here is outputted here. Same thing the user types in is passed to this function. So, let's save. And the test does pass. And of course, we don't want to trust it. So, let's break this in various ways to make sure this is really confirming that the send handler is called. First of all, let's not call the onSend handler at all. Let's just comment that out entirely. When the form is filled up, yep, I've been called with message text. So, expected to be called with hello world, but it was called zero times. Okay? Instead, let's have onSend called, but not pass any arguments to it. What happens then? We expected it was called one time. We expected it to be called with hello world, but instead it was called with zero arguments. So, that's nice and descriptive where we're missing. And then say we type in a bad argument here. We passed the string bad. Now the test says, oh, we expect it to be called with hello world. Instead, we receive bad. So, there's a
testing library ESLint plugin that provides a lot of help recommendations for ESLint for your
react testing library. And one of the things that it will do, actually, it may not be
testing library. It may just be a Jest. I think it's a Jest ESLint plugin. And if you said expected send handler to have been called, it's actually going to warn it for you. I don't have it set up here, but it'll warn you and say, hey, it's probably better to do called with. Like, you should probably specify the arguments passed. And even if you aren't passing any arguments, you could say called with empty argument array to confirm that it's called with no arguments. Usually, if you're calling a function, you probably care about the arguments that are being passed. And so, it's a good idea to call to have been called with. Nan in the chat says, you can also check to have been called times. And that is helpful as well. So, here we can confirm that it's called one time instead of two times. It'll fail if the number of calls is correct. I'm not aware of a way to check both the number of times it was called and the arguments that are called as well. And usually, in my cases, you can do both. I mean, you can do one after the other at times. Confirm that it was called one time with these arguments. And let me fix it here so we have the right arguments here. So, you could do both. That is the most robust. Usually, for me, the arguments that's called with is more important than the number of times that it's called with. But sometimes something is called several times that I need to know. Or I want to make sure there's a risk of it being called too many times.
react 18 calls to APIs. Anyone use effects? Although, actually, I think that doesn't happen in tests. So, forget that side trail. But, yeah. So, I often reached for it to have been called with. So, now the next thing is sort of a side note here. We now have this test. We followed that approach that I recommended of
testing one behavior per test. But now we have a lot of duplication. So, what can we do to remove this duplication? It's not identical, but there's a lot of setup where we get the user, we render out, and we do this interaction. This is duplicated exactly. So, what can we do? We can pull out. And I would recommend don't pull out duplication right away. But when you find that it's duplicative and it makes it harder to read your test, it's good to pull it out. So, what can we do? First of all, there's some things that are different in these tests, but they don't need to be. And so, we're going to create some common setup that is just the setup that's needed to get both of them working. And the fact that some don't need some of those details isn't too important. So, let's do this one bit at a time. It can be helpful to do these refactorings and keep the tests passing. Let's pull out message text and put it up in the describe block. And then, let's use it down here. Instead of the hard-coded string, let's put in message text there. We save and the test still passes. So, we know that it's still working. Another thing we can do is we could put a user in a let variable up here. That's not going to make too much sense until we pull up the setup code. So, let me do it this way instead. Jess does have a before each function that you can use to perform setup that runs before each of the tests down below. But there's actually a lint rule that recommends not calling render in before each because it can be mysterious. It makes it not so obvious in your test that rendering is happening. And so, instead, it's better and recommended to have an explicit setup function that you call. So, that's the approach I'm going to show here. Render and fill out. I like using the word render in the setup function just so we can see at a glance rendering is happening. And so, I'm going to copy and paste the setup up into there. Do all this setup. And then, oh, it should be an async function because awaiting is happening in there. And then, down here, we're going to say await render and fill out. So, we're getting some, let me save just to get the formatting to happen. But we're getting some ESLint errors. We don't have access to the send handler here. So, there's a few ways you can handle that. You can return the send handler out of your setup function. In this case, what I'm going to do, that would work fine. I'm going to declare send handler out of here. But in the render and fill out, I'm still going to assign it. So, then, now, send handler is now available because it's available up there and it's available down there. Now, we save. The test passes. Now, let's use this setup function for the other tests as well. Render and fill out. Await, render and fill out. And we remove it. And then, we get, so, I screen get label by text message to have value empty string. We save and it works fine. Because user event is not needed down there, we don't need to put that in a variable that's accessible elsewhere. We can just keep it in render and fill out. So, now, this line wrapping makes it a bit hard to see. But now, our tests, we've removed most of the duplication. The only duplication is this common call of this render setup function. The before each approach removes even that. So, it's just the expectation in the test. But
react testing Library recommends, and I'm good with going with that recommendation, it's a bit clearer to have this explicit call to render. And, certainly, if the setup gets longer, or if your tests get longer, it can be nice to see at a glance when you look at the one test that's failing. Ah, I see that the first thing that happens is we're rendering and filling out. And so, now, you can explicitly, you can even go to definition with your IDE or Intelligent Editor to go up there. So, it's a bit less mysterious. So, that's a recommendation from the linter that I'm happy to go with. So, when you want to, this can be a way to remove duplication from a test. And it allows you to have the benefit of these nice, simple
testing one behavior per test. It gives you nice output ingest as well. So, you can see a description of the behavior of the component, a description of the contract of the component. So, you can see when the form is filled out, it clears the message field and it calls the onsen prop with the input test. That's a helpful
documentation to read if you're a developer newly working on this component to see what's going on, to see what the expected and current behavior of the component is intended to be. And, again, just to reiterate, I don't always remove this duplication right away from the very start. But any time the tests start to feel cluttered, I will remove duplication like that. And I find that's generally useful. So, let's talk about, oh, I need to show you the one thing that I promised you I'd show you, mock name. So, what is the purpose of mock name? What happens if it's not there? Well, if I remove it, when the tests are passing, everything is just the same. Nothing is different. But let's take a look when the test is failing. Let's comment out that onsen again. So, check it out here. When this fails, it says expected and I didn't point this out before. Expected send handler to have been called with expected. Expected hello world, number of calls, zero. And then it shows the code down below. Expected send handler to have been called with message text. So, now what happens if I remove mock name and save? Now it says expected just function to have been called with expected. So, send handler still shows in this code snippet down below because that's the name of the variable. And Jess is able to output the source code of the test that's showing here. But the first line here, it says expected just function to have been called. Now, similar to a question that was raised earlier about SVGs, if we only have one mock function here as we do here, maybe that's super obvious. And we're thinking about this test. And I'm talking about it at length. So, you have this all loaded into your head. So, you know the mock function we're talking about. So, maybe in this case, it's totally fine. I have other code though where there's an object with maybe four or five different mock functions. And I want to confirm which one is being called. This is common with
api clients or
api.get,
api.posts,
api.patch, things like that. And so, in that case, it helps readability at the very top of my test to be able to see expect send handler to have been called. It's test readability that helps your test fails to save you time and to prevent misunderstandings. I like that little bit of extra readability. So, I tend to put in mock names. It won't hurt you immediately if you don't. But if you find that you're in a situation where you've got a mock and it's failing, it's kind of an indirect concept mocks anyways. And so, if you find that expect just function to have been called is confusing or leads you down the wrong path, the wrong assumption, adding mock name to your test, to your just mock functions I found can be very helpful. So, that's why I prefer it and recommend it. All right. Let's summarize what we saw about just mock functions real quick. So, here's our calls to send handler with a bit of a different test name. This is before I pulled out that duplication. We set up the message text, but then we create the send handler. We create the just mock function. And then we pass it into the component. And after we interact, what we're confirming in this case is it's not visible output to the user. It's a function call. We confirm that the send handler is called with the right message text. That's the contract of the component. The component, what its contract with the rest of the application is if you pass in an on send prop, I will call it at the appropriate time. That's what the component guarantees the rest of the application. And that's what we're
testing here. So, one thing I want to say here to talk about the idea again of implementation details. So, someone could say, and actually there was just a question a week or two ago on Stack Overflow where someone asked about this. The user doesn't care about an on send function. The user doesn't care that send handler was called. And so, are we
testing implementation details by using a just mock function and checking that it was called? Well, here's what I would say about that. The principle of tests like a user makes sense in general. And I don't disagree with that. But when we think about whenever you're
testing a portion of your application, you're choosing to test one part and not another part. And if you're
testing a component, you're
testing that component as it stands. So, what will we test if we weren't
testing the on send function for a new message form? Maybe in your code, if your application you do have, you wire it up and you pass an on send function in and it saves something to Redux or saves something to
apollo graphql or saves it to a server. But if we test that, we're not
testing. And you might have written the new message form to hard code that in. Maybe new message form pulls in the
api and calls it directly. But we chose in this case, the new message form has a prop passed in for on send. So, as written, the new message form component does not know. And in fact, it's reusable. It could be used in different contexts. It does not know. So, if we've chosen to test a component rather than test our whole application, we have to work with the component contract. And the contract of the component is, hey, rest of the application, you give me an on send prop. It wouldn't make sense in our test to wire up new message form to the
api here because that's duplicating code that's elsewhere in the production code. That's not part of the new message
forms contract. If we want to document how new message form is written and what it's written to do, calls the on send handler is exactly what we want to document. That's the contract there. So, that's why I really teach this idea of
testing the contract and thinking about the inputs and outputs of a component. Sometimes the input and output of a component is other code. That's part of it. And in fact, I would say, just to stay on my soap box a little bit longer, the user also doesn't care that there's a component with new message form that's rendered out using JSX. We are already in the realm of something the user doesn't care about when we're
testing components. And I'm not saying that as a criticism of component
testing. I'm saying there's a trade-off there. End-to-end tests with a tool like
cypress or similar tools are the most realism in terms of they're running a full application and they're interacting like a user. And in fact, to be fully realistic, you need to connect to a real
backend, maybe a real credit card processor. Maybe you don't want to do that on your automated test that runs every time you save a file. So, there's realism that is good in some cases, but sometimes you don't want the full realism. Sometimes you want to test just your
react application decoupled from the
backend. Sometimes you want to test just a component as decoupled from the rest of the application. And just like once you're
testing a component, you've brought in the fact that you're importing a file from a specific location and you're using JSX to render it out. In the same way, if that component is set up to take in a function prop, that's part of the contract, the inputs and the outputs. And so,
testing with a Jest mock is an absolutely appropriate thing to do in that case. So, I'm still working on how to explain this well. Maybe I'll do a blog post on this someday. But that's why I would argue that in this case, when the prop passed in as part of the contract, the external interface of this component,
testing that with a Jest function is not an implementation detail. That's absolutely
testing the contract of the component, which is totally an appropriate thing to do. Let's talk about exercise two. And hopefully, if folks are running behind a little bit, we'll just catch up as we go. I actually tweaked my answer a bit to experiment with some of the things we were discussing in the chat. So, there's definitely multiple ways to organize the code for this one. So, let me show you what I've got and then field any questions or feedback from you all. I'll stop to field that chat before we get into lecture three and part three. So, I am not a very original exercise maker. And so, this form is just so extremely similar to the message form from before. It's a similar situation. We press send, it clears the title field, and it calls the create handler. Both of those things happen. So, let me walk through what I set up in here. I did put movie title, the string out as a constant at this level. I removed anything else at that level because I wanted to try out how that worked. But this as a string, it can't be changed and is not intending to be changed. And so, I felt safe putting that out there. Then I have my render and save async function. I set up the user, user event variable inside there. One reason is I don't need it outside of that function. And another reason, I checked the user event 14 docs and they didn't, I couldn't find a place where they specifically said make sure to set up a new variable for each individual test. But that seemed to be the pattern that I saw. I didn't see them setting it up once and using it all the way through. Because I think then you could just use the old approach where you didn't explicitly call set up. So, I set up user inside the render and save function. Set up a mock. Something I didn't mention before, I forgot to, is so in our set up function here, we set up the create handler mock function even though we're only asserting against it in one test. So, it's not needed for the other one. The other test doesn't fail when that's not provided. But when it comes to shared set up, it can be useful to say this is what I need to set things up in general. And then even if not every test asserts on every single thing, the shared set up can still be useful. I type and I click as before. And so, this time I decided to show the approach where if you don't want any mutable variables in a shared context, any mutable variables shared across multiple tests. So, what I did is I created create handler inside the render and save function. And then I return it. So, this is the approach if you don't want to have those let variables at any level. If you have a set up function, you're going to need to return the things from it that are used elsewhere. And so, that's not needed for movie title because that's immutable. And so, that felt safe to me. The create handler, I return it. So, in the case of clears the title field, I don't need any of the return values. And so, I just await it. But in the case of calls the create handler, I await render and save. And then I extract the variable that I need, which in this case is create handler. So, that's a way to explicitly pass things along. Sometimes I hesitate on that because it's like duplication in terms of like, oh, I've got to create it, got to return it, got to pull it out here. And it's less code to write if I just have that let variable up above. But also, it's harder to see where it comes from. It is more explicit here in the same way to see, oh, this create handler comes from here. It comes from this function. I can go up there. You can avoid a situation where maybe multiple places in the code are writing into it and causes your test not to be
testing the same thing, the thing you expected to be
testing. So, there's certainly benefits to that. My team on a
react Native project that I was on encouraged me and encouraged us to take this explicit approach. And it worked all right. Before the screen
api in
react Native, that only made it to
react Native very recently. You had to pass, you know, you had to destructure all these getters from the render function. And so, passing those along became pretty tedious. But you could also assign it to a variable called utils, et cetera, et cetera. There's a lot of tradeoffs here. The point is, this is all about different ways to organize the same tests. And you're optimizing for readability, optimizing for maintainability, optimizing for, you know, not false positives or false negatives. And, you know, certainly removing let variables can make things more understandable and predictable. So, I'm all in favor of a team taking this approach. One thing to be aware of is if you say, oh, I got to return three or four values and that feels tedious, so I'm just going to write one big function instead and test all the things. Well, yeah, you can do that. Those variables are just available in that one test function's local scope in that case. But then you have a test where a lot is happening. You don't get this nice output documenting what the separate behaviors are of your test. And that's possible. Like, that's doable. But just think about what are you trading off when you have very large test functions or medium-sized test functions and just make an intentional choice. I'm checking the chat now. The test body is practically the same for tests calling the onCreate handler and cleaning input. How valuable is actually splitting the test into two? Jonah says, if something bugs out and allows you to pinpoint the error easier, faster. Yes, I would agree with that. If you have multiple assertions in one test, Jess will show you the line it fails at. One downside is that, and I'm not against that, that can work. I do write tests that have multiple assertions and then even a sequence of multiple things happening sometimes. One thing that happens there, unless there's something about Jess that I'm not familiar with, the first time an expectation fails, the rest of the function doesn't run. So, the other expectations are not checked. And so there, at that point, you just see one failing test in your output and you may not know, oh, of the 10 other expectations that are run, two other ones fail, but eight of them succeed. If those were separate tests where you're
testing one behavior per test, you'd be able to see, oh, these two are failing and those eight are passing and see that at a glance. Another thing I find is that it tends to be more descriptive as far as, I find for me, having these one test per behavior helps to really specify all the different things the component does and helps me to make sure I'm covering all the functionality. When I have one big test that says it handles submitting the form, maybe submitting the form does five or six different things. And so then it's hard for me to write just a test name that covers all of that. So I just say, it can submit the form. And so then hopefully I make all the expectations in there to cover all the behavior of the test, but it's easy at that point to say, oh, this test is already long. It's probably good enough. It's probably
testing enough. And I might not be actually asserting on and specifying all the functionality of the component. If I'm disciplined, maybe I can, but I like to put approaches in the place that can aid me and keep me accountable to be disciplined. There is certainly a speed implication to having this, but if you have a really large
react application, I don't know, different approaches. I feel like if doing large tests that are
testing a lot of many things at once, is that going to bring my test suite speed down to something maintainable? Maybe, maybe for now, maybe it's going to get big larger regardless. So maybe handling the speed issue sooner rather than later would be better. And again, I would really recommend folks to think about not saying you always need to do it the way I'm recommending. I really say make the right choice for your project. But if all of your test functions are very large and have many things going on, how maintainable are they? How easy are they to read? I can see at a glance here in this test. And of course, these tests are extremely trivial, so it's pretty easy to see. But even thinking about this test output that says the two things that happen when you press send versus this test output, it's like, can I see at a glance what's happening here if this was just the one test that was here? I'm a fairly experienced programmer. I don't really want to have to parse through this like handles submitting. I'm going to tend to be too lazy to read through this and to figure out like, oh, these are the expectations. These are the two things that are needed. To me, clears the title field and calls the create handler is easier to follow than parsing out these two different expects and understanding what's happening. Again, not saying you have to do it this way. Of course, you are totally free. But that's the trade-off that I see. And this is a very simple test. If this is a much longer, if this was 20, 30, 50 lines long, it's a lot harder to read through it to understand exactly what the behavior is that's being asserted on. So I would say that there is communication and understandability value to having one behavior per test. And so it's an option to consider. So if you're going to big tests that make many assertions for them, just know what you're trading off so that you can make an informed decision with the rest of your team. That's all I'm saying. I'm catching up on the chat now. Having the test cases and the actual spec sheet for the functionality component is pretty handy, especially if you display it in a
storybook for stakeholders or QA UX product owners. That's cool. And I will say, for the sake of transparency, I haven't done that. It's really only been me that's been looking at the test output. But there could be team setups where you're really intentionally saying, what are all the things that this forum needs to handle? And maybe that communication is helpful for your case. Cool. Thank you for the interaction, everyone. That was great. Let's keep moving on because we have less than an hour left. And I want to get through this last section to get you something helpful. And I'm opening IntelliJ accidentally. All right. So we talked about different types of external function and what to do for
testing with those. We handled function props. We're using just mocks for those. So that's one part of the... Like a lot of movie series, the last part has become split into multiple parts that are longer. We're pulling out Twilight or a Hobbit here. So calls to external functions. This output, we've made partial progress, but there's more. What about effects? What about module mocks? So let me say what I mean. So for external functions, you might have a function prop that's passed in, but also it's very common in
javascript to have functions statically imported from other
modules. And one example of this is very common is hitting APIs. And so I'm kind of addressing multiple things that are common situations here at once. So what do we do? Do we want to hit the real
api? Maybe it's good. I mean, think about that principle,
testing like a user. The user doesn't care that anything happens internally, even within your
react application. They care that the right thing is saved to the... Well, they didn't even care about the server, but they care that their
data is safe. Maybe we do want to hit the real
api in our tests. What I would say about that is if you look at
cypress's
documentation for the end-to-end
testing tool, they have great information about the trade-offs on integrating with a real
backend versus mocking out your front end. And there's pros and cons, and they kind of actually recommend doing both at the end-to-end test level. So what I would say is if you want your test to hit a real
backend, end-to-end is probably the level of the application you want that to happen. Probably you don't want that to happen in your component tests. Very, very typically, your Jest tests, your unit tests, your component tests, I would really recommend running those on a continuous integration server and blocking the merge of your pull requests to make sure that they pass and that they flake out sometimes, that you address that to get them passing reliably. That's a really important thing for safety. But you can't have predictable, reliable tests that run on CI and block your merge, I would say, if you're hitting a real
api. Maybe some of you all have done some great engineering to make that happen, but generally that's not worth the effort. So I would say at a component test level, I would recommend mocking out and not hitting the
backend server. So now we have to say, okay, how are we going to prevent our component tests from hitting the server? Again, just to get on my soapbox for one more second, note, if we're not hitting the real
api, we're already not fully
testing like a user. We're not fully
testing realistically. So that door has been opened. Now we have to weigh the trade-offs. So there's actually a lot of different options for mocking web service requests. And I want to lay out the different options for you. I'm sure there's many more, but I will lay out some of them and talk about which is the one that I teach in this workshop. So think about your app is organized this way. The rest of your code calls maybe an
api client module. Maybe you have a module, a
javascript file where you set up your
api client. And then maybe that calls through to Axios, the HTTP library. There's analogous things. Incidentally, if you're using
graphql, there's analogous things in that setup, but we're just going to show this approach here. Axios uses underlying HTTP request support from the browser. And then ultimately it goes out to a separate web server. So actually there's options to mock all the way along the chain here. So if you want to mock at the level of when you go from the rest of your code to an
api client module, you can use a Jest module mock, which we're going to see shortly. If you're going to Axios, you can mock out with a library-specific mocking adapter like Moxios. There's a different one that my current project uses that I forget the name of it. So anytime you're connected to a specific third-party library, there can be a mocking library specifically for that. When you're mocking an underlying HTTP request support, knock is one library option. Mock service worker is another popular one. When you're in
cypress and end tests or component tests, when you're in
cypress, they have their own
api for mocking. And then even at that level, there's even other options. You can have all your
javascript running as usual, but instead of hitting a real separate web server that lives out somewhere, you could have a local web server that's booted up on your machine or on continuous integration, or you could have a mock web server, a server that's not a real one. It just has some hard-coded or configurable responses. When they post this, return this fake record back. But at that point, that's a mock that lives outside of your application and all your
javascript is running the same way. So there's a ton of different options here. I want to talk about, let me see my slides. I want to talk about Jest module mocks, and let me talk about why. It's very easy in
testing to say, hey, I'm using Axios, or I'm using
apollo graphql, or whatever, or I'm using Firebase. How can I mock that out? And it's like, all right, well, we've got to find a library specific mocking approach, and did someone create it, and are they maintaining it, and is it a usable
api that's helpful and understandable in my test? And so it's like, anytime you want to test something, it's like, I've got to do more work to figure out a mocking approach, or just hit the real thing, or just give up on
testing. And people give up on
testing because this stuff is hard. What I love about, and I'm not saying I only use Jest module mocks. I use others. I use knock on my side projects. What I like about Jest module mocks is that they're a general purpose solution. If you're importing something from another module locally, or a third party, you can use a Jest module mock. So that is a catch all, no matter what. And I'm sure there's exceptions, but mostly, whatever weird, crazy legacy code library somebody's interacting with, if there's an import, you can do a Jest module mock. So I like to test that because all these other libraries, hey, check out their readme's. You can totally use them. But for Jest module mocks, if you learn that once, you will always have an option for mocking. You will never be stuck because nobody created a third party mock, or it's poorly maintained. And I want you to always have an option, because I want you to always be able to write a test for something, so you can get the coverage that you need, and the safety and protection that you need. So that's why we're talking about Jest module mocks. Not because it's the best option, because it's the most general option. And so have it in your tool belt, along with these other things. So Jest module mocks. Those are described in the Jest docs. And let me show what they are in the code to describe what this means. This is exercises. We want to be in the lecture sandbox. That's here. So let's describe what we have here. In lecture three, we have just a very simple
data loading component here. We have an
api module we created. I'll show you that. It just very lightly wraps Axios, but it gives us our base URL config, but it exports Axios exactly as is. So we import that in our component here, and then in an effect, we load it. Now, I know that
react has been talking about use effects for
data loading, and the
react beta docs, if you haven't seen them, are super, super helpful in this regard. It is recommended by the
react core team to do
data loading using a framework like
next.js or a
data loading library like
react query, because there are a lot of edge cases and those libraries handle for you in a very helpful way. I'm using use effect here as a very simple illustration, kind of like the simplest approach. And in fact, one of the nice things about the approach of
testing we're going to see is that the approach you use to
data loading is kind of an implementation detail. So once we write the test we're going to write, you can refactor it to use
react query. I actually have, oh, I don't have it here, but I actually have tried refactoring the code to use
react query to confirm that it actually works. So it's another example of tests that in this case, the exact
react library mechanism to do the
data loading is an implementation detail and our tests allow us to change it. So that's pretty nice. So let's take a look at our test here. Test for the widget container. We want to confirm that it loads widgets upon first render. So I mean, setting aside in
testing details, we want to render the component. We want to confirm that widget one and widget two are present. And so when we run this, it says we're unable to find an element with this text. Let me get my notes up here. Um, the element is not found. Now we haven't done any mocking yet, but still like this component does hit a server. So, and there's no off, so it should be coming back. So what is going on here? The first thing to know about and learn about is about asynchrony. Um, we, this component is renders and like the web services, the web service is not replied right away. We're actually making a real HTTP call all the way to the real server. It cannot respond immediately. And he didn't probably
react, wouldn't even rerender if so. So we can see, we can confirm. I often, whenever asynchrony is involved, I often use some console logs to understand what's happening. So let's, let's do that. Let's say console log sent request, and then console log got response. Let's see what happens when we save. We see that the request is sent and then the test fails. And, uh, actually check this out. It says, cannot log after tests are done. Did you forget to wait for something async in your tests? So not only do we not see the got response output in, um, Jest or one of the libraries is hopefully letting us know, um, hey, something happened after your test finished. So we need a way to wait. So one way we could do this, just, just to see that your different options, um, I wouldn't recommend this, but you can, um, use, uh, a promise. So you can return a promise from a Jest test. Uh, there's like a done approach as well, but let's do this. This is kind of just for illustration purposes, so we can understand better what's going on. So we've got a promise here. We're going to resolve at the end and actually we want to do a set timeout. So you could have a timeout where you say, I'm sure I haven't typed this wrong. I'm sorry. So yeah, so in the test, we return a promise and Jest is going to wait for that to resolve. We have to build it from scratch. And then we set it up from scratch. And then we set a timeout that says, Hey, let's wait for a thousand milliseconds for a minute before we check for widget one and widget two to be present, um, and then resolve after that. So that gives the test time to wait for the response to come back. And I notice here, we get sent requests and got response earlier. There's a warning here. We're going to ignore for a second, but the test is in fact passing. So we are actually waiting, um, for these responses to come back from the server. And then let me just break the, make sure the test actually fails. Um, yeah. So it says, uh, well, there's a lot of output here. If we find unable to find an element with the text widget high high. So that never comes back. Um, but widget two is in fact found. So we are now, we're now waiting for the server to response. We have an option. Um, so what are some of the downsides of actually hitting a server and waiting like this? So if the, if the server takes too long, the test can fail sometimes. And that actually happened in the last workshop I was in. I had to bump up the time from 1000 milliseconds to 2000, because I wasn't on, uh, I was on a slower internet connection. So as a result of this, though, this slows down your whole test suite. This test is not done until those milliseconds are done. Um, and so also if the, so that slows down your whole test suite, if you're adding 4,000, 5,000, 10,000 milliseconds to many, many tests and old style end to end tests rain into that a lot. And the test suite's got really slow. Talk about slowness, like set up, not very slow. Having every one of your tests waiting multiple times for 10 seconds. That's extraordinarily slow. So you don't want to do that. And also if the remote server goes down, your test is going to fail. So what is an alternative? Now we can use Jess module mocks to replace the
api call with one we create that we can control more directly. So first let's restore our tests to what we did before we added the promise and get back to that situation. Oh, I got an extra curly there. So let's put that back. All right. We got that, that logging happening afterwards as well. So let's look at what how Jess module mocking works. Um, so first we say, first let's do this Jess dot mock slash
api. So this mocks out the module. This is not a Jess mock function that we're creating from scratch. We're saying, Jess, please mock out this module. So it's the
api module that's available from the same folder. I looked at the other
api modules looked at the other
api file. Ignore that one is this one in this folder. And so let's, let's just save that change and see what that changes in and of itself. Cause it's kind of indirect and it's good to understand it. When we call Jess mock on the
api module, now it says in our code saying, when we call
api dot get widgets that happens here, it says, cannot read properties of undefined reading then. So what this is saying is we're still able to call
api dot get, but it's no longer returning a promise. So we can no longer chain a van on it. Um, this is because Jess dot mock and there's details of all the different ways you can do named and default exports that I can't summarize very well, but in general, a Jess module mock mocks out all the functions that are exported out of there, including in this case, the different, um, functions that are part of this
api object. So
api dot get is available and it just returns undefined by default. So now we can control it and we can give it the response that we want. Um, so the first thing like we want this to succeed. So we want to get access to
api here and we can say import
api from
api. We're doing the import just the way we do in the production code. And what we get here is the mocked out version of
api. So we can say here
api dot get dot mock resolved value. This works on all Jess mock functions. If we create it from my hand or recreate with a Jess module mock. And this says, Hey, when
api get is called, um, return a promise and have that promise resolve. So this should allow us to call that then let me save and show what this does. Now, um, we get an error, can't be properties of undefined reading
data. So that's different. We've gotten past the dot van and we're getting into here and we're calling response dot
data. And, but it's saying that, uh, response is undefined. And that's, as we said,
api get should resolve, but we're not saying any value that it should resolve with. So by default it resolves with undefined. We want to give a real resolved value, the value that we expect to come back in this response into this promise. So we can do something like this. Um, we can say mock resolved value
data because with Axios, the response object has a
data property and that's what it goes into. So the
data property is an array and we can say, ID one name, widget one, and then do two, a second widget. So we're controlling, Hey,
api to get should resolve with this
data. And that should be the
data that satisfies this test. So let's save. I think I have an extra curly there. Oh, and I noticed my Jess runner completely aired out that time. I'm not sure exactly why, but that failure was enough that it couldn't recover. So we're going to restart the test runner again. All right. So now we have a new error, unable to find an element with the text widget one. So that's surprising because everything is proceeding along. We should have the widgets. We could actually log them out if we wanted to, but why aren't the widgets being shown on the screen? Um, well, we can see the dawn output here. Again, the value of this clear output from the get by, um, we see there's the body, the div, there's the list, which is this list, but no list elements are rendered. So it's like no widgets are available. So the reason for this is because of the way that
react rerendering works. This is, this promise is set up to resolve right away. And so there's not a number of millisecond delay, but promises resolve. And I'm going to get these details wrong because there's things about the
javascript event loop and micro tasks and things like that. But basically, you know, it doesn't resolve immediately. Like, you know,
javascript is running, it's single threaded, it gets to the end and then, Oh, a promise resolve time for me to kick out a new, kick off a new thing that triggers a
react rerender. So there's a little bit of asynchrony, asynchrony needed, even though there's not a weight of a thousand or 2000 or 10,000 milliseconds. So what we need to do is some way to give, give
react time and say, Hey, this widget one won't be available right away. Wait for it to become available. And the easiest way we can do that is to make this an async function and we can use what's called a find by instead of a get by. The difference with find by is it returns a promise and it waits, it retries repeatedly until the element can be found. We just need to use an await here. And we do this right in the middle of the expect. So we can still, we're just waiting for this element widget one to be available. And then once it is, we do, we confirm that it's visible. We can still do those same visibility checks that we do before. So this await find by is going to give
react time to rerender. When we save it runs, let me scope down to lecture three, because I think that was a failure from one of the other tests lecture three, we got sent request. We got response and the test is passing as always. Let's not test it. Let's change the
data to make sure it fails where if the
api returns the wrong
data, it'll be rendered out wrong. Another one of the nice things about mocking out the
backend versus hitting a real one is you can control the
data we're controlling. Here's exactly what's returned. That makes it easier to handle different
data scenarios. You can have this reject to handle error scenarios. So you get a lot of control when you take any of these mocking approaches within the
javascript code base itself. When I save here, it reruns and I get the error. I'm able to find an element with the text widget two, and we can see that widget one and widget 999 are rendered out. So we've seen the test fail the way we would expect. Now, if we do pass in widget two correctly, it should be cleared up correctly. Notice that I didn't change the second one here to use await find by. And that's because once the first one succeeds, like we wait for widget one to show up. And once widget one shows up, widget two is ready. We don't need to wait again. At some point in the past with these tools, if you had two awaits in a row in an unnecessary way, you'd get a warning. I think that might be fixed now. So let me try that out to see. If I don't care whether there's a wait or not, I just care that eventually they both show up. And that does in fact work. Yeah. So you could do this parallel to see if, and this way just says, hey, just make sure that both of these eventually show up. But if you do want to say, hey, I'm confirming that as soon as the first one is there, the second one is there as well. Doing a get by for the second one works because in the way async await works, once we're past this line, we've now waited for this to happen. And this happens synchronously right away. It checks right away to make sure that widget two is available. And we can remove our log statements here to clean up our output. And now we just get to the point where we're confirming that we're loading the widgets upon the first render. And now there's no wait at all. There's no set timeout. There's no number of milliseconds. This asynchronous loop needs to happen once here, but we're proceeding as fast as the
react asynchrony will allow us to proceed. There is one more thing we need to test though here. This is actually one of the downsides of using Jest module mocks that other approaches like knock and mock service worker handle for us. What if we got the URL wrong? Because we just said here, when you call
api.get, mock resolve value. So what if instead of widgets, we said fidgets? What if that's not the correct URL on the server? We save and the test still passes and we don't want it to. We want to say, hey, I want to make sure it's the widgets endpoint on the server that returns this. So we can make it confirm it. So there are more complex ways to do implementations of Jest mock functions that could check the arguments and stuff like that. But one straightforward way to do it is you can make an assertion on the
api.get. You can say, expect
api.get to have been called with widgets. We saved this and it fails. It says, oh no, it wasn't called with widgets. It was called with fidgets. We go back and save widgets here and then it's done. So this is an important thing to think about because with any of these mocking approaches, oftentimes it can be easy to be insufficiently clear to say, hey, as soon as a get request is made to the server, return this
data. As soon as a post request is made, return this
data. Or as soon as a
graphql query is sent, return this
data. And that could get your test to pass. It could prevent the issues where there's no
data available there. But it might be too general. You might not be asserting on all the inputs and outputs. Think about it this way. The call to
api.get is an output. It's going out of the component to somewhere else. And do we care whether the right endpoint in the server is hit? Yeah, we do. Our application is not going to work if it's not the right endpoint. And so it's important to assert on not just that when the right
data comes back. And let me stop and say it this way. A call to the server is an output from the component, in a sense.
data coming back in response from the server is an input. And then what's rendered out is an output. And so it's not enough for us to say, hey, when this
data comes back from the server as an input, make sure that this is outputted on the screen. We also want to confirm, hey, make sure that the output of the call to the server is correct as well. That in some sense is even more important with
data that changes things on a server. When you're saving a record to the server, you want to make sure that all the appropriate
data that the user entered is sent in the right
data structure to the server. Otherwise, your front end will work in and of itself, but it's not in reality. And it's not going to talk in a way that the server will actually work with. One of the things that I like about NOC as a mocking library is it combines these together. So you very easily say, when I hit the server and call slash widgets, return this
data. So I've started using NOC pretty significantly my own tests because of that. I think that's a nice benefit. Okay. We're ready to go back to slides. And again, for the sake of expediency, I'm going to keep going and then field the questions in a group. I think as is often happens for this version of the workshop, our last exercise, you may need to take that on your own. We may not have time to do it together, although I will stay available. So let's go back to the slides and process what we just saw. So here's a widget container, and let's go this way to see it. So we're taking Jest mocking, and I'll back up in a second to talk about general mocking approach, because there's principles here that apply to any approach where you're mocking at the
backend. But when it comes to Jest module mocks, and specifically, we import the module because we're going to need it. And then we mock it out. We say Jest.mock, mock out
api and replace it with Jest mock functions. They don't have any behavior by default. So we need to and want to, this is exactly what we want to do with a mock, and we want to configure them with what the resolve value should be, what
data should come back. Then in order to wait for that, because there's asynchrony involved, in this case, there's a promise because it's related to a web service request that needs some time to come back. So we need to await. We need to await a find by to make sure to give it time until that comes up on the screen. And we can't just proceed ahead. We need to let the
javascript event loop tick over. And then at the end, we also confirm the URL that's called on
api.get. Now, although if you're using knock or mock service work or another approach, the syntax of here would be very different, but the goal would be the same. The goal is I want to control what's going out to the server and coming back from the server and control it in an individual test and make assertions on what is sent out, what comes back, and what is rendered out on the screen. We're thinking about that component contract again. Just to briefly show some of the options, these are available for all mocks, but specifically when you have a mocked module with a function in it, you can mock a return value. So that's useful if it's not promise-based. It's not asynchronous. You're just mocking out a module that returns something right away. If you're mocking out a UUID coming back and you want to control the UUID value. You can just pass your own promise and mock return value, but if you're using a promise, it can be more expressive to use mock resolve value or mock rejected value. That causes the promise that comes back to resolve or reject respectively. It's great for success scenarios and error scenarios. Let's talk about those find buys. So early in the workshop, we saw the get buys that check for an element and they will error out right away if it's not found, giving you helpful error output. We also saw query by functions and same prefix for all the different suffixes that will give you a null back if it's not found and this is recommended to be used when you expect something not to be found because that allows you to make an assertion against that. Third, we have find functions as well. These wait for an element to appear and they error out if it doesn't appear within a certain specified timeout. They check repeatedly and so you saw, you may have seen, we weren't waiting for an extended number of seconds for that find by to resolve. It very quickly resolves, but it'll wait up to four seconds, I think, by default to wait for something to come back and that's useful in case you do have a set timeout in your production code, but find by is useful for this asynchronous stuff and you can wait for it. So this is an approach to first function statically imported from
modules and again, I'm conflating a few different things, but like what do you do when you're importing a module? What do you do in that module has asynchrony involved and what do you do when that asynchrony comes from a web service? We've shown a number of different tools together for how to handle it. Now functions from hooks and I promise we're not going to extend this, splitting up the last part of the movie series indefinitely, so I'm kind of glossing over hooks a little bit, but in a sense,
react hooks are just a way of organizing code and so how do you address when you're using a hook in your code? It's going to be very similar to the tools we've already used. So if you have a hook that gives you
data or a function, maybe you're getting
data from a context or maybe you're getting a function from a context that is called into from your function. That's like a prop value or function. It's not passed in through props, but in your test, you can control what's in that context and what's coming in and so that's inputs and the calls to functions or outputs. So in those approaches, those kinds of hooks, you test them very similarly to how we have with prop
data and prop functions or a hook might ultimately call a static module function. Maybe it calls that
api client that you have or a third party client like
apollo. If that's the case, then you would test that hook in the same way as you test the module and in many cases, if your code is pulling in a hook, it's recommended by
react testing library, consider that hook and implementation detail and that's kind of what I'm advocating here basically is like, okay, you've got a hook going on, but what happens at the next level after that? Is that
data from another context that's passed in? Is it a hard coded third party library? And you can use all the approaches we've described above. Now, I don't always follow that approach. I'm mentioning something we're not going to have time to do together. I do sometimes test hooks directly when it's first party code that is intended to be reusable, current client project, we're doing that significantly, but sometimes a hook is just a hook. A hook is just an implementation detail and your code doesn't need to know. It just needs to know when there's this
data in the context, what do I show on the screen? When I fill out this form, what function in the context is called or what is sent out to the server? That's glossing over things a little bit, but I'm available in the Discord if I've been unclear, happy to dive in, but I think you will find that the tools we've been equipped with, they help you and the use of those will jump out as you're diving into hooks. So that's the different types of external function. I'm sure there's more I haven't thought about, but I think that covers a lot of the cases. And so now we finally come around to handle the last output. We've talked about the inputs and outputs,
testing the contract of a component, the props that come in and the user interaction events that go out. Oh, sorry, those go in as well. The props and user interaction events that come in and the rendered UI and calls and external functions that go out. Let's take a look at the third and final exercise. And then we will go into wrap up some final points to recap what we've gone through and leave time for any other questions or thoughts, suggestions that folks might have. All right, so some screen sharing, good. So the movie lists, loading movies on first render. I did do a test of creating as well, but I'm going to hide that just to keep us on track. This one is going to look a lot like the example we said. Again, I am not the most original exercise creator, but I wanted something else that was analogous, because even just getting the basics of these things wired up, if you haven't done it before, or if you take a different approach to asynchronous, it's good to learn and try it out. So movie lists, we also want to load movies upon first render. We are taking the approach here where we've got an
api in just the same way. We're pulling it in, we're mocking it using just module mocks, and then we're setting mock resolve value as well. Again, as we saw in another test, I'm pulling in just enough
data. So this is interesting. This is something interesting that falls out of here. We tested movie row, and I think this came into the exercise description. I said, movie row has a title and it has the added today button. So okay, movie list uses movie row under the hood. What should we test? Here, I decided just to test enough to see that the movies are present by
testing the name. I didn't pass in any added today properties on those objects, and I didn't check for that. Because we're
testing movie row already, I didn't feel the need to test that again at the movie list level. You could, and as you're, I don't mean to say this too sarcastically, the larger the larger your app gets, you could test all the details of it at every single level or just only test from the very start of your application. But that's a lot, and that will slow down your app as well. That's exponential increase of every time a movie row is shown, I'm
testing all the aspects of movie row again. I don't think that's useful. The focus here is on
testing for movie list. What is the code in movie list itself? What are the unique things that movie list does? No, this test doesn't ensure that we're actually using movie row underneath it versus just outputting the text directly. But I'm not worried about getting that wrong. These are the kinds of judgment calls you need to make. Sometimes it might make sense to test all the details or maybe just one other detail of the movie row, or maybe it doesn't. But yeah, I mock the resolve value. I render out the movie list. This time I actually put the
api.get assertion above the wait just to illustrate that that happens right away. If this happened immediately upon render, or if it happens when you click something, for example, you can confirm that that just mock was called because that is called synchronously. That call is called in the same part of the event loop. I guess if you're awaiting a click, then you're awaiting already. That already gives
javascript time to do stuff. But even here, we render and then in the same event loop, we immediately check for
api.get have been called. Yes, it has been called. The promise just hasn't resolved yet. But we await for find by to wait for movie one and movie two to be there. Then we're good to go. We assert on them and they're present. Let me just get Discord pulled up as well. But yeah, that's really straightforward. I don't think I want to jump into the deep integration test, but you can check this out in the solution branch of the repository later if you like. This is basically an approach where I'm adding the new
data, which we tested in the form that when we add the
data on the create handler is called, this is
testing at the movie list level, create is hooked up. We are calling
api.post. If you're
testing the create functionality of movie list, you are
testing that
api.post is actually called and
data is sent to the server. You can check that out if you like to see how a test like that of sending
data to the server would work at that level. But I want to move on from there. Let's take a look at the slides. Wrap up to summarize where we've been together today. Designing effective tests. The hope was to introduce both the APIs of
react testing library as well as an approach to thinking about them, the tests that are high value and low cost. Because if you've ever been there, there's nothing more discouraging the tests that feel like only cost. They never catch a bug. They never ensure functionality. All they do is just cause you to have to do three times as much work anytime you change anything. That's frustrating. That's not helpful. But I'm hoping and I think and believe in my professional experience has been that if you take the approach of
testing the contract of a component, you're going to get tests that are higher value and lower cost. It's still some effort, right? Writing tests still take some effort, but they're going to give you value. I'm not trying to assert that you have everything you need for
testing forever from just the three hours. I really have found that
testing is irreducibly complex and there's always more to learn. And as I've said, there's more in your specific context with your team, project, organization, and just yourself. There's things to figure out what works for you. But I hope that I've given you some helpful foundations. These are the things that I've found are the basics that I need and that I try to equip others with for effective tests. So what did we learn together? We talked about what does it mean to test the contract? This concept of thinking about the contract. It's not quite the same. It's similar to, but not quite the same as test like a user because a component is already down one level removed from the user. It's also not quite the same as don't test implementation details because what's an implementation detail from the user standpoint is not an implementation detail from the component standpoint. A component is interacting with other components. It has props. It has JSX. It has functions that it calls. And so if you were going to test a component rather than only doing end-to-end tests, you're already working with something that's not quite what the user interacts with, but it is
testing the contract. And I think having tests like a user and don't test implementation deals in the mix in your thought process are absolutely, absolutely valuable. But I think test the contract gives you a specific way for thinking about what is an implementation detail in this case? What is the user concerned about where the user might be another component using this component? We also talked about why test the contract. Let me, I got distracted here. Azeager says, can I find the solution somewhere? I'm not passing my test and would like to compare with your implementation. Oh yeah, Lottie said there's a solution branch. Yes. So if you pull down the solution branch, and I should put that in the readme if it's not already there to let people know, I just kind of hid it off of the main branch. But the solution branch is there that you can compare against. So please check that out. And I'm available on GetNation's Discord, the conference Discord, and other ways to be met. I would be happy to field questions from any of y'all later if you spend some more time working on exercises and it's giving you any errors or issues or anything like that. We also talked about why test the contract, which I guess I just said. Coming back again,
testing a contract gets you out of the implementation details of what exactly is happening in the component. What exact style props are added? Can I get to the states to figure out the internal state of the components? Or what exact test ID is being rendered out? The contract is like, what is the input and output? So what's the interface of this component to the rest of the application? And that allows you to do refactoring inside the component. Third, we learned what are the inputs and outputs of the component? And I'm sure there's others, and some of you all sliced it in slightly different ways and slightly different ways. Those are all valid as well. But I really summarized it as props and user interactions coming in. And remember, rendered UI and calls to other functions going out, which includes calls out to servers. And I think that category can be helpful because it can be really overwhelming to think about a component and to say, this is something that I run into. And it's a very reasonable question. Folks will say, I have this line of code. How do I test this line of code? Or they'll say, my code coverage tool is showing that I'm not covering this line of code. That's very valid. I want to cover it. How can I test this line of code? And what I really try to instill in people is that that mental model can get you stuck, because then you can think about, okay, how do I get to that line? How do I execute that line? And that can tempt you to dig in through the external interface and get to implementation details. But if you think about, well, what is the purpose of that line of code? What does it cause the component to do? If I comment it out or delete it or change it, what is the difference to the output caused by what inputs? And so that test the contract mentality really helps you to avoid digging into details and answer a question when you're like, I have no idea how to test this line. Again, it still can be challenging circumstances to come up. It happens every week for me. Something new that I got to figure out in
testing. But I think
testing the contract is a very helpful mental model that will help you there. So to continue to the learning, in case you can't tell, I love talking about
testing, especially in
react and
react Native world. So I would love to be in touch with you. You don't need to sign up for anything. But like, if you go to the workshop webpage here, there's ways to get in touch with me. Mastodon, LinkedIn, email address, if you ever want to reach out in the future. There's the test double newsletter, if you'd like to sign up there. I will still be in the conference discord. If you'd like to join my personal discord for other
react and
react Native
testing people, we'd love to hear from you there. So those are ways for us to stay in touch. Again, just to share those links I shared earlier, in case you maybe care a bit more now because you didn't know me at first when we got started. This is a book that I've written about test-driven development with
react testing library. All those parts are very much the same we've done. But it adds
cypress into the mix as well. How do you have these end-to-end tests and component tests working together? It adds Redux into the mix. Not because everybody uses Redux, but because the idea of a
data layer separate from your UI can be helpful. So if you like my approach, if you're interested in trying out
tdd, again, the intro chapters are available for free. You can download those. It has a lot of theory about why
tdd. And there's a $10 off link. This will be available, I forget the timeframe, but it's at least a couple of weeks. So you can check this out. It's linked from the workshop webpage. You can get it there. Ebook or paperback. Amazon won't let me put the paperback on sale. So the paperback is full price. But yeah, if that's a helpful resource for you, please check it out. And I am reachable if you have questions about anything in there, or if I can enhance the book to make it clear. TestDouble, we'd love for you to stay in touch via the newsletter. If you'd like, sign up for testdouble.com slash newsletter. You'll hear things about
react and
javascript and Node and Golang and Ruby
testing and software
architecture and team stuff, agile stuff going on there. Thank you all so much for coming. Thanks for signing up to give me a chance to speak. I love sharing these things. I hope something got you thinking, whether you agree or whether you disagree or whether you have better things for me to learn from to do better. I really appreciate the chance. I appreciate the conference organizers giving me the chance to give these workshops and to connect with you and share with you. And I hope we stay in touch online sooner or later. So thank you all so much for taking this time. Please feel free to give me feedback if there's something that would have been more helpful to you. And maybe I can still give you a pointer asynchronously to get you to something. Have a good one. Bye.