This course is no longer being maintained. We recommend the testing section of the Intermediate React course instead. We now recommend you take the Intermediate React, v4 course.
Transcript from the "react-testing-library Solution" Lesson
>> Kent C. Dodds: So let's refactor this a little bit. So I actually didn't mention the generate method, but that's just to generate a login form for you so you don't have to use the Chuck Norris username if you don't want to. But I'm going to keep using that. No, that actually is kind of an important point.
[00:00:22] If the particular user name and password aren't relevant, then having them in your test can be a distraction. I might think, maybe there's an actual user name, Chuck Norris, and that has some significance. Or something. So by abstracting away the things that are not relevant, you can kind of communicate that it doesn't really matter what this is.
[00:00:44] So I'm gonna say loginform here. And then we're also going to pull in render and simulate. And rather than ReactDOM.render, I'm going to just use render from react testing library. And here we'll call this container. And we'll also need getByLabelText to get our form fields and getByText to get our submit button, which I'll show you why here in a second.
[00:01:13] And then we don't need to pass the div. Fun fact, you could actually pass a container here and render to something specific. If you created a div and you wanted to render to that one for some reason, that's actually how you could rerender a component, like change its props.
[00:01:30] So you just rerender the same thing to the same container and that'll be like updating the props. Cool. So then we can get rid of this div, we don't need that any more. And to get our usernameNode and our passwordNode, we're gonna do that differently now. We'll say, the usernameNode is getByLabelText, username is capitalised.
[00:01:54] But it actually doesn't matter. It will be lower case. User doesn't care if it's capitalized, all caps, anything. So the library doesn't care either. So it will compare them lower cased. You can also pass a regex. I didn't mention that, but you can pass a regex or even a function that accepts the element and its text and you can check it that way.
[00:02:15] Okay, so then this one will be Password. And we can get rid of that. And then for the form node, so we could add a data test attribute to this one. If you want to, you could do that. I think at some point it's reasonable to assume that we're not ever going to have more than one form in this log in component, and so it'd be okay to just say, container.querySelect your form.
[00:02:42] Just get me the form that's in there. I would not recommend doing firstChild.firstChild. That will also get you the form. But then that ties your test to the structure of the DOM, where the user doesn't care about the structure of the DOM. So what the user cares about is finding that form, what we care about is finding that form too, so that's how we are going to do that.
[00:03:07] And then I'm also going to get that submit button. But I'm gonna get it by the text, because. Sorry, I forgot to mention this. The form component actually renders a submit button. And I don't think that. Yeah, maybe you didn't actually do anything with the submit button, but we're gonna do that.
[00:03:27] I'm gonna show you something with that. So anyway, this form component renders the submit button right here. So we're gonna just look for it by its text. And so we'll say getByText, Submit.
>> Speaker 2: It's case sensitive?
>> Kent C. Dodds: No, so it's totally insensitive. Cuz your users don't care. Could be all caps and they still can understand what's going on.
>> Speaker 2: That is a regex though, right?
>> Kent C. Dodds: So, this is a regex. Yeah, and we can do it that way too. If we wanna make sure that it is submit and nothing but submit and it's case sensitive, then this is what we would do. Say it starts with submit, ends with submit.
[00:04:10] And if we want to make it case insensitive, we add an i, but the general case, I don't really care. Cuz somebody could come in later and say submit form for the elements. You can decide if that matters to you if you want your tests to break if they change the text in that kind of way.
[00:04:31] But as long as it contains the text submit, then it's good to go. Okay, cool. So then the rest of this right here is gonna be the same. Rather than using raw DOM APIs for this, we're gonna use React's simulate method to simulate a submit on the forum.
>> Kent C. Dodds: And our tests are passing. So one thing that is in here that we didn't talk about before. But I have this assertion that this submit button has the type of submit. The reason that we have that is because if it doesn't, then clicking on the submit button will not submit the form.
[00:05:14] Because the submit button doesn't have an onclick handler on it. The form has a submit on it. And in addition to that, if you don't have any button at all or if it's not typed submit, then you hit Enter and that also will not submit. So it's important that the submit node has a submit on it.
[00:05:33] And so there are some other things that if we were to try and take this submit button and click on it, our form actually will not actually submit. And this is due to event delegation and how React does that. It's kinda frustrating. And we can resolve that in our next exercise.
[00:05:54] But that's what we're doing so far. If you wanna use submit, then you have to do some extra, or simulate, then you have to do some extra work to make sure that things are gonna be okay.
>> Kent C. Dodds: Okay, so what questions do you have about this? Actually, before you ask your questions cuz I know you have many.
[00:06:14] And I know you have lots of feedback and elaboration to submit. But I just want to ask what do you think about the testing library and the experience of writing a test with this? I'm seeing nods, but that doesn't, like yes, my assumptions were correct, this is a terrible experience.
>> Speaker 2: So I like all the helpers to get by label text, and all the documentation there to find these different selectors. I'm not very familiar with Enzyme, so I didn't really have a comparison there. A question I have is, I wasn't quite sure why would you use getByLabelText.
[00:06:56] I wasn't sure what that was finding versus getByText. GetByText is just actual text that's rendered to the screen versus?
>> Kent C. Dodds: Yeah, let me, that's a great question for clarification. So getByLabelText. The object of this query is to get a form control that is labeled. And so, here as an example, we have a label and that has an HTML for attribute.
[00:07:23] So, what it's doing under the covers is it looks for all labels that have the text that you specify. And once it finds one, it's going to check the HTML for attribute and find the input that has the same ID. And then that's what it's gonna give you.
[00:07:37] Or the text area, or the check box, or whatever the case may be. If, then there are alternative ways to associate a label to an input for accessibility purposes. And that is, and also functional purposes as well. And that is for the label to have an ID and then the input to have an ARIA labelled by.
[00:07:58] And then there are couple other mechanisms to associate levels with form fields. But yeah, so for accessibility reasons, you're using a screen reader, you come to an element, if it has a label, it'll read the label. And if it's not associated properly, then it doesn't read that label.
[00:08:16] And so you want to make sure that these things are associated with each other. On top of that, when you have a form, if the label is associated, you can click on the label. It'll put focus on the input. So there's functionality associated with that too. Yeah, does that answer your question?
>> Speaker 2: It does, yeah. Thank you.
>> Kent C. Dodds: Okay. And then on the getByText, we could actually get the label element itself with getByText. So we'd say get me the element that has the text password. And that would get us the label element. But we don't really operate on that, so we don't care.
>> Speaker 2: If you have multiple elements with the same name, does it return an array then?
>> Kent C. Dodds: That's a good question. No, it does not. I suppose I could see a situation where that might be useful. And so maybe that's something we could add. The challenge there is, what I might do with whatever it is that you're trying to accomplish.
[00:09:19] Maybe you have rows of form fields, so they're the same, or something like that. So what I do is, I'd find the row that I want first, and then I'd use the DOM testing library. Because you can specify a container for the scope. So getByLabelText, here's my container, I want you to do that searching.
[00:09:40] So that's probably how I'd go about that. There might be a use case to make that a little bit easier though, so. Other questions?
>> Kent C. Dodds: Okay?
>> Speaker 3: I would say validation on this library. Because using Enzyme to run some of the tests that I’ve written in the past, I think I was looking for an input.
[00:10:08] And I’d have to find the actual input versus. Like, so there wasn't really any way to test to make sure the label was displaying what I was expecting. Just kind of makes sense that we should be really testing it from the user standpoint. Like this is the label for this input, so.
>> Kent C. Dodds: Yeah, so actually, when I first wrote the library, everything was just data test IDs. I didn't have getByLabelText or any of that. And then somebody commented on my blog post about it. And it was like, hey, this is actually a really bad idea. You should be. Well, first off, you shouldn't be changing your production code for tests.
[00:10:51] And it's like yeah, I know, but it's kinda better than CSS class names. And said, no, I'm not suggesting class names. I'm suggesting doing this with as if the user was using your stuff, using ARIA attributes and things of that nature. And that made a lot of sense.
[00:11:10] So that's where we got the getByLabelText and getByText and things like that. So, I'm glad that you liked that, I do too. All right, cool. So now I am excited to hear what you learned. Well, I guess some of you kinda mentioned what you learned. But what else did you take away from this refactor that we just did?
>> Kent C. Dodds: Yes, Peter?
>> Peter: So, like people have mentioned, this library is obviously very opinionated about how you should be testing your, and sort of specifically like a form and input elements. And I like that you have to, it communicates that pretty well. So sort of what you explained about how you have to, how it sort of limitation of React simulate that we have to go in and grab the form node.
[00:12:16] Even though the form node is sort of transparent to the user, they really don't care about the form node.
>> Kent C. Dodds: Yeah.
>> Peter: The library communicates that pretty well, I think, by, you know, we have to use the container and use querySelector to do that. It's not as convenient.
[00:12:33] And it sort of gives us a smell of like that problem.
>> Kent C. Dodds: Yeah.
>> Peter: It explains why it's necessary, but it's [INAUDIBLE].
>> Kent C. Dodds: No, that's a good point. It's kind of like, this is hard on purpose.
>> Peter: Right.
>> Kent C. Dodds: Yep.
>> Peter: And it's not really hard.
>> Kent C. Dodds: Right.
>> Peter: But if it's one extra step. And if you stop and think about it you can see. So I liked that.
>> Kent C. Dodds: Awesome. Yeah, that's a good point. Another thing that is hard on purpose is getting like an instance of the component. I have had some cases where getting an instance of the component was the only way I could possibly test something.
[00:13:11] Without doing some really complicated stuff with other tools and things. And so, what I think makes a tool valuable, or an important part of a valuable tool, is when it makes the less optimal things hard, but not impossible. That's what I love about React, is there's the React way of doing things, but sometimes you gotta use that library that's using DOM manipulation.
[00:13:42] And so I'm grateful for refs and componentDidMount to be able to kind of use these escape hatches. And that's kind of what I'm happy about that with this library is that it makes some of these things kind of harder, but not impossible to accomplish.