Check out a free preview of the full Enterprise UI Development: Testing & Code Quality course

The "Counter Exercise & Solution" Lesson is part of the full, Enterprise UI Development: Testing & Code Quality course featured in this preview video. Here's what you'd learn in this lesson:

Students are instructed to implement component tests to validate the correct initial count is displayed and that the count is reset when the reset button is clicked.


Transcript from the "Counter Exercise & Solution" Lesson

>> All right, so I believe your next mission is, we got this exercise right here, we're gonna do a few more things. There's the very simple version before I did that abstraction. So ideally counter also takes an initial count and has a reset button. So now that you've watched me do it for a little bit why don't you spend a good, let's say eight minutes see if you can get these two tests to pass?

Could you peek in a counter.solution? You could, should you? You should not. If you did need to peek somewhere, you can look at what I did up here, right? Or if you need to see that complete file, that is in my Fun Bucket of things. Here I actually called the typeof separately, which is nice, I should have done that before too.

The other thing that I did in this version as well and I should bring over to my file and I'll talk about real quick before you do that exercise is we can also export everything else. So now I don't ever need to bring in testing-library/react when I go into my kinda smaller baby version of this.

Now this will export everything from react testing library plus our custom render. So now we can just use that for everything and not be like is this one in testing-library/react and this one was in my utilities? Now we just use test utilities and one of the things you'll see in the larger app is, and I like this pattern a lot, is this test directory in both my tsconfig.

And my vitest.config, you can see that I say, I can just do test/utilities. And it looks like it's a third party dependency, but it's not. And it is like I get that from anywhere without having to traverse up and down. I just do test/utilities, I get everything from React TensorFlow library, plus that monkey patched version of render that I can use.

So now if we want to render the component with an initial count, this is very much like what we did before. To be clear, though, for those following along, which is because we talked a little bit about making the custom render. What we'll be implementing now is closer to what's in the file, counter.extension.tesla.tsx.

This one with our custom renderer solution is without the custom render that we just wrote. So either one of them work, they both pass. If you wanna kinda look at the final results as we live code, but our implementation is closer to extension than solution. Cuz we did things slightly outta order, cuz I'm live coding and stuff.

Things get a little wild sometimes, cool. So we can render the component with an initial count. And honestly, copying, and pasting never hurt anyone. So we can go ahead, and at this point we give it an initial count. And you need a gotta love TypeScript. I mean, theoretically, IntelliSense would have done it in JavaScript, it's not important.

And we'll give it, a 4,000. What's it angry about? I believe that wants a number. So then we can go ahead and type 4000 here, right? And this test passes, right? It's just basically a way to get warmed up with what I did before. And this one, again with the resetButton, is a variation on a theme, but let's do it together.

And now, remember, in this case, we're not using the custom render, so we do need to swap that one out. So we can go ahead and just do ./test/utilities. And that utility solution is the one that I wrote with us together. So anything generally speaking, if you're looking for what we're live coating, the closest approximation is in those solution files.

Where I have a written version you can use as well, but I have the one that I wrote. So at this point we can render the counter, I'm gonna give it another initial count because otherwise, this comes to our classic problem. Is zero because my resetButton worked, or is it zero because my resetButton didn't work.

[LAUGH] Right, now I could choose to increment and that's actually fine test, right? Because again, you now know with one test that does all those things. And maybe someone will choose to lecture you about how your test should be isolated, and you don't have to listen to them, right?

You do what you want, if you find value in it, have at it, right? And then we'll say and one thing that you could do is, I don't necessarily the before all because I'm stuck with it every time. I can't pass arguments to it, but if I was doing this in of course, 32 tests, hypothetical number.

Could I make a function called setup where I pass it in a value and it gives me back some number of things I know I'm gonna need? Yeah, that's even easier because then that setup or whatever you call that function, you can command click on it and then jump up to the implementation of it.

First is like a before hook, good luck finding why it's in ClojureScope, right? But yeah, so you could do that if you wanted to. Maybe you should, there's what's the rules, which is start with no abstraction, then don't over-optimize light too early, cuz all abstractions sacrifice some amount clarity.

So then what we wanna do is we will await const, that's not how JavaScript works. User, I don't know why I need to hand be my own prettier here and that was a and then we will look for that. I could do it inline, I could store it in a variable, it all depends.

Again, if I was using these buttons for a lot, I might do them all and might just export an object that has everything. Get by role, and we will say that we're looking for a button and the name should be reset. And again, if you wanna do what I did before, I can do that way I'm not tied to the peculiarities of how it's formatted.

What if it was like CSS making it all caps and it was really. Whatever, or somebody changes the, I don't want my test to break cuz my stuff doesn't work. I don't want my test to break because we change our stance on title case. You know what I mean, so I can do that.

And right now the test pass because we did find we were successfully clicking the button. We did find the current count, right, the only thing left to really do is expect that current count to have text content of zero. And our test passes. Now we know that we can pass in an initial value, unlike if we were just thinking on a test coverage perspective, also we don't know that anything works because Yeah, I'm just that good that I spiritually knew it was gonna pass.

Also, I've done this 1000 times, literally with this component, this seems off the cuff, but it's not. So we've got that all in place unlike if we think about test coverage perspective and again, break stuff up into small tests if it is helpful for you, don't do it because someone told you to, right?

So we could theoretically grab that incrementButton. Instead of passing an initial count I mean, you could do it if you wanted to, right? Let's do the most, how much can we get away with here? And I'll treat myself as some white space in a second incrementButton. You know what?

Click it again. And then let's go ahead, If I've already rendered the thing, might as well put it up here. Just have some, [LAUGH] fictitious version of organization here. And here, we can go and we'll go ahead and first of all, expect. Current count to have text content 67, what you angry about?

Yeah, can you click something, right? ResetButton, right? Now with one test I am relatively confident in most of the functionality of this component, right? If this was the only one that I did, I'm morbidly curious how much test coverage we got out of one test, in this case, simple components.

Sure, and we'll do a larger thing on test coverage in a hot minute, but we can do this and we'll do --coverage. --runs actually see stuff. We got to do the two dashes, right? I have 100% test coverage from one single test. Sure, ridiculous component, got it. Right, but let the people tell you, you need to break stuff into a bunch of tiny tests.

It gets the job done, I have confidence in this. Say I could refactor this into my heart's content and know that as much as one can know anything, at least the functionality works. Now I could screw up the CSS on this in all sorts of untold ways. Again, 100% test coverage doesn't really mean anything, but I think it's a good start.

And we'll play more with test coverage later. It'll be kind of an interesting experiment that we play around with, Dustin.
>> Do you have a preference in general for selecting DOM elements is it generally best to just select by role or you otherwise use the data ids when it's inconvenient?

>> Yeah, we use a data id when there's nothing really like a random div doesn't necessarily have a role, right? You can select by the text and something, but is it the thing you think it's going to be? Since we got this test and since it's running, let's just get it spinning up again.

Yeah, particularly because this one doesn't have anything written in it other than the count, which I is what I'm asserting. So I could hypothetically, let's go look at the component for a second. Right, let's say I needed that header for some reason, I'm pretty sure. When I go off-roading with live coding is always when the towards the end of the day is when we know that the best stuff always happens.

So let's find out how this goes together. We could say const header=screen.get ByText, As angry about that, I wonder if I need the full string in this case, because you notice that I can't find it. So again, even testing all these things on there-
>> Regex it.
>> Yeah, I'm wondering if that TM at the end is part of my problem.

All right, so here I can actually find it, I think it was just maybe because it was a partial word. Let's see what happens when I put the TM back in if that was my issue. Being clever. Yeah, so we'll do the partial match in that case. I think otherwise I'd need the full string.

So yeah, you could find it, but in this case, because I'm trying to assert that it's zero finding the element that says zero and making sure that it's zero. I guess I could say, yo, something should say 67 on the page and that would do the trick too, but this feels the best for me.

But yeah, so we have this header, and now that's actually adding. Again, we had 100% test coverage and that header could have vanished. So to be clear about 100% test coverage, it's not as great as it sounds, but we did get there with one test. So one thing I will do real quick is just to show, we could do something, I'll just put it up here for a second.

We'll say nourish count equals number. Cool, colon number rather. And then we can say, for instance, return, user, currentCount, IncrementButton, resetButton. So now if I need to use this with different situations for the initial count, I could just use this function, I will get all the things that I need.

Whatever is helpful for you, I think makes sense in this point. One last thing and then I'll give you a little bonus thing to play around with as a extra credit assignment as well. It'll show up later, but that's a fun thing. One thing I just wanted to show you real quick is I want you to look, let's just look at the fully completed extension one.

This is a version of what we just wrote without my little side quest, and how it renders a react component, right? This one, Is how it does the same thing, but with a svelte component. The only effective difference is I'm not writing JSX in here. I'm just passing it the component class, and the props go in an object next to it.

So JSX is the only, the tiniest amount of JSX syntax is the only real like effective difference between how I would write these tests for React and how I'd write these tests for svelte, vue, what have you. So if you're worried that, we're talking a lot about testing React, we're not.

It just happened to be a React component.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now