Testing React Applications, v2 react-testing-library
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, v5 course.
Transcript from the "react-testing-library" Lesson
>> Kent C. Dodds: So here's my solution, actually it comes from some utilities that I built on top of Enzyme that didn't allow some of these things. And I decided I didn't need Enzyme as a dependency of this thing. So, here's the dom testing library, and nothing reacts specifically here. There's actually a view testing library that's built on top of this.
[00:00:25] There's some discussion to create an amber testing library that's also going to be built on top of this. An angular testing library, all of these things because these are really great utilities. So, just as kind of an example, here straight from the docs, because this isn't react specific its just using div and our HTMLl stuff.
[00:00:47] But, so let's see, right here is where the test is actually useful, this is all like. Here's our component that has event listeners and stuff like that. So, maybe it would be better just to show you the react version, we've got these utilities in here. Just know that lots of the query selector utilities and stuff, are coming from the dom testing library.
[00:01:14] So if you need to look deeper into the docs or anything, then you can look in there. Okay, so here's an example of using the React testing library. So here we're rendering this Fetch component, and inside the Fetch component, there's a button called Load Greeting. And so we're using React's built-in utility, so in ReactDOM, you have this simulate thing.
[00:01:38] And so React testing library just re-exports that. So check out the React docs for simulate. But you can simulate a click, [COUGH] on the element that you get by text. And so we're finding the button by its text. So, right there when I first thought about this, I thought I don't know if I like that but here's what converted me.
[00:02:04] If we're really thinking about using our tests, using our software the same way that our users are, then how do they know which button to click on? They have a page full of buttons. How do they know which one to click on to accomplish the task? They read the text, and that's how they know.
[00:02:21] And so our test should do the same thing. So how do I know of all the things that are rendered inside this container, how do I know which one I should click on? I reference it by its text. So there are other ways to grab it, and so we'll look at those.
[00:02:37] But that is the suggested way for finding elements like buttons. And there there's, remember that flush promises thing that we wrote a while ago? This weight utility is similar to that, except it is a little bit better because it waits until your call back doesn't throw an error.
[00:02:57] And so if you try to execute a get by text it will throw an error if there is no element in there by text. There's also this test ID that I'll explain in a second, it will throw an error if there is nothing by that test ID. So this will wait until this no longer throws an error.
[00:03:15] So we clicked loading greeting, we're waiting for the greeting to be loaded and that's exactly what the user would do to. They click on it and then just wait until they get that greeting text. And then we can make our assertions once that's happened. And we have some assertion helpers too, where we can say get me a note and this literally just returns a domino.
[00:03:36] There's no in enzyme you have a wrapper and it has all those utilities on top of it here it's just DOM nodes that you're killing with. So we get by a TestId, I'll show you that again here in a second. Expected TextContent so you can change something in particular.
[00:03:52] And then we can verify that the button has disable on it. Whatever that's you might need and you can even still use snapshots with DOM nodes it all works great, which was a really nice surprise. I didn't know that you could do that, [LAUGH] until after I wrote this.
>> Kent C. Dodds: Okay, so you wanna look at some of the queries, real quick and then feel free to interrupt me if you have any questions. So it exposes a render and that render method returns a bunch of utilities. I pretty much always jest destructure the ones I need. You could call it render_utils, or whatever you want, but I was jest destructure the things I need.
[00:04:31] So the container is just, you created document doc create elements, div, that is exactly what this is. It's just that div that you created, and that is the container. And then it appends, or we render to that container and so if you wanna get the root element that your component is rendering that like root div for example here.
[00:04:52] If you wanted to get this element then that would be container.first child and that's regular dom API stuff. We also expose unmount, so there's situations where you wanna make sure that you're not or where you can test avoiding a memory leak. And so you can unmount and then verify that you're not client set state again or something.
[00:05:14] And there's an example of this in the test of the react testing library that you can check out. But yeah, that's good for clean up then stuff. And then we have get by label text, so bunch of ways, and this is the best way to get form elements.
[00:05:34] So how does a user know where to type the username? Look at the label, it see that it says username, and they find the associated element. And if it's accessible they'll be able to click on the label and it'll highlight the associated element. If it's not accessible then you won't have a label and this won't work.
[00:05:55] Or you could even have a label but not have it attached appropriately, and that is also not accessible. And so that wont work with get by label text. So this is actually, not only encouraging you to write your test in a way that users use your code, it's also encouraging you to keep your applications accessible, so that's great.
[00:06:20] And so you can associate a label with a four in react that will be html4. You can do the reverse, so arial-label by, and then rapper labels even, you have to do a little bit of extra work for those to work properly. And then there's also aria-label but that's not recommended because people who aren't using a screen reader can't see that label.
[00:06:46] But if that's how you're labeling your stuff then will work as well And then you've got placeholder text, I think you get the idea. An input has a placeholder, and you can select by that. Generally not recommended, for accessibility reasons also. But then getByText is great for finding links, finding buttons, whatever the case may be there.
[00:07:11] I get by all text if there is an image you want to verify is when you are doing something and then test ID. So this is a different subject all together. So far we're able to find four main parts, we're able to find buttons. But sometimes there's like user name display field.
[00:07:26] So you have like at the top of the page you'll have where that user's username appears. And so you might be able to do a getByText if you know what that user's username is, and that would probably work just fine. But there are some situations where it just isn't reasonable to use any of these queries to find the elements that you're looking for.
[00:07:51] And so in those situations you can, in your source code, add this data test ID attribute. And what this data test ID attribute does is actually nothing useful in the application. But then in your tests you can reference that with a get by test ID and that'll get you the specific node.
[00:08:11] So, you might be asking yourself, okay, well, why don't I just add a cost name, and then container.queryselector costname? And,the reason that I don't like doing that is actually this, I have a blog post all about it making your UI tests resilient to change. And pretty much it's because whether or not are class names on our elements in our web apps is specific to styling, which it's not, class names are used to classify the elements, it's not just styling.
[00:08:50] But like by and large the community that's pretty much all its used for, all the class names are being used for. And I'm actually pretty happy with that, I think it's nice to have something to have something specific for styling. And so the problem that I had in the past, is we would have these class names.
[00:09:10] We would use those class names like you'd have btn-disabled or -primary or something. So then I'd be I got that, just used that in my tests. And then somebody comes in says, okay, this is no longer a primary, it's a secondary and it breaks the test. I don't like that, because there's nothing about the cost name that communicates to anybody that this is for testing purposes.
[00:09:37] And so by putting a data test ID attribute on, or prop on your components, you communicate that really directly. And you can refactor that, move it, it doesn't matter where it appears in the container as long as when people see that data test ID. They know exactly what that's for, and they'll make sure that stays on the right element.
[00:09:57] Yes, question?
>> Speaker 2: So we're talking about actual production code that would have data test ID as part of it.
>> Kent C. Dodds: Right, yep. So- [CROSSTALK]
>> Speaker 2: And.
>> Kent C. Dodds: That's a common, sorry, I won't interrupt you. [LAUGH]
>> Speaker 2: No, I just wanna clarify. So you're potentially going to be populating your production code with additional data attributes, is that the type of thing that you would wanna sanitize?
>> Kent C. Dodds: Yeah, good question, so, yeah, it is a very common question that people ask me about this and I address it in a blog post. But first off, I would say, let's just assume that we do ship this to production, where's the harm in it? First off, the user doesn't even know, maybe it's a couple of extra bytes.
[00:10:47] Most of you are probably including these libraries that are enormous anyway. This isn't gonna be your bottleneck, you have other places where you can spend your time on shrinking, your, size of your apps. And in addition to that, this is actually really useful for end to end tests anyway.
[00:11:09] And so it's good to have them available for your end to end test to use and verify that things are working. Even smoke tests that you run against production just to make sure that things are doing okay, actually it's really useful to have those. But if you just really are uncomfortable with having these things in the DOM, for one reason or another, I do have a reference to a plug-in that you'll appreciate.
[00:11:33] That will plug-in-react-remove-properties, and you can configure it to remove. Data test or data test ID and just during production build, just remove them all.
>> Kent C. Dodds: Cool, any questions so far, yeah?
>> Speaker 3: With these different methods of selecting elements can you use the same methodologies in like a tool like Cyprus or Selenium or [INAUDIBLE]?
>> Kent C. Dodds: Yeah, so that's, ha, ha, so there's actually a Cyprus testing library. Woohoo, I pushed a bug fix last night for the workshop. But yeah, so there's a Cyprus testing library, it has all these same query methods. And yeah, the test read really nice, so I'm really happy with that.
[00:12:24] As far as Selenium is concerned, you select elements a little bit differently in Selenium. I think there are some abstractions to make it a little bit more normal. So there might be a way to make something like this work with Selenium. But I will definitely not be working on that, because I'm done with Selenium, I'm all Cypress now.
[00:12:46] So yeah, potentially, but yeah, definitely Cypress is. Any other questions or comments, observations?
>> Kent C. Dodds: All right, so there are just a couple other things I want to show you then we can jump back in and refactor our test to use these abstractions. So when you render, as you have right here, you wrote this.
[00:13:12] You create this element, this div, this container, and you render to it. But that container doesn't actually ever find it's way into the document, it's just this div that's living in memory. And so, that's actually part of the reason why we Have to dispatch the event the way we do.
[00:13:28] That's part of the reason why here in a second you're gonna use simulate to simulate those some events and I'll explain that a little bit more later. But if you wanted to actually render something into the document because you have the jQuery installed. And that's gonna be the query the document.body for certain elements and stuff, then you can just render into document.
[00:13:57] The problem with that is once your test is done, there's nothing there to take it out of the document again, once you're all finished. So you'll want to use this clean-up method to, what the cleanup method does is it removes everything that it inserted. You can also use unmount to manually remove it but I find that cleanup is easier, you just after each cleanup and it'll remove everything.
[00:14:19] It's a little bit of extra work, but it means that we can use this other utility that I'll show you a little bit later. So that things can be a little less simulated and a little more actualated. Yeah, that's not a word, but.