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

The "Unit Testing 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:

Steve live codes some of the solutions to the Unit Testing exercise. The remaining solutions can be found in the `exercise.solution.test.ts` file.


Transcript from the "Unit Testing Solution" Lesson

>> Let's learn some things about JavaScript, shall we? So we can do an npm test in here. Might even spell it correctly. And you can see that it runs all the tests. One pro tip is if you just one of the one that's entitled like exercise, right? I can do either npm test and I can do exercise and it'll just do a partial file match.

So now, you can see anyone's word exercise in there will work. And then the ones that don't have that won't run. I could write exercise.ts, whatever. It all happens. The other option is hit H, and then I can do it by file name, so on and so forth.

We'll look at some other ways to find just the test that you're looking for as well, but that's enough to get us started at this point. So let's mark this first one as active and we'll hit save and our tests have run. And you can see that it breaks.

Why does it break? Cuz JavaScript doesn't have the concept of anything that's not afloat. So as we all know, 0.2 + 0.1 obviously equals 0.3, a bunch of 0s, and then a 4, right? Part of the subtext for some of this is, we all know what happens when your tests get flaky, right?

You stop writing them and then you turn them off, right? So some step one is to get to the point where you're not turning your tests off because it's hard to up the test coverage if you're constantly turning them off. So kind of, then the answer is kinda hidden in this list up here.

I'm gonna guess that has toBeCloseTo. ToBe, CloseTo, cool. And that is roughly looking for a certain amount of precision. If you hover over it, IntelliSense tells us that it also will take a certain number of digits in which you wish for it to be close to. So for instance, I might choose 2 if I was doing money, hypothetically, right?

In this case, as an example, I don't really care. The one thing that I don't know if I noticed until just right now, is if you look at the VI., the type is a JestAssertion. Again, once again showing that everything we're doing in vitest, if you love Jest, that works too.

And it'll do the thing, cool. So we'll save this one and look it up here. If we need to see if something is an instance of a particular class cuz if we look at createPerson, all that really does is instantiate a new person class or constructor rather, depending on, that's where I accidentally tell you how old I am.

And so we can do expect this person toBeInstanceOf(Person), right? Anything that has that as a constructor in the prototype chain will pass in that case as well. This will be somewhat useful to us, momentarily. Great, and then we have the describe block. What does the describe block do?

It basically, if you looked in that UI before, it organizes your test. So it indents them and puts them all together. You can skip a whole bunch, or you can todo a whole bunch, or you can only a whole bunch. It's just a way of organizing your tests.

Do you need to do a describe block? You do not. If you just wanna write it or tests at the top level, what Jest and vitest do is they just wrap that all in a describe, right? A file is effectively wrapped in a suite, but then you can nest these as much as you want.

You shouldn't, but you could, and they will share any kinda hooks that we'll see in a little bit as well. Cool, and so in this case, we wanna see it should include a Backlog in one of its many statuses, right? And so in this case, we can do expect(board).toContain.

The word Backlog, right, and this is effectively doing Backlog.
>> Is there not toInclude?
>> In this case, it is toContain, but after the board.statuses, let's find out together. I know it's toContain, which I will never at this point in my life remember strings have a .contain and arrays have a .include, or vice-versa.

So strings have a includes and arrays have a contains, so,
>> I don't know if there's a contain at all.
>> That's includes, I don't know. It's the test assertion is toContain. We could also do the opposite of that, which is we can assert that it does not have that thing in it, right?

Or not toContain. Cool, as you can see, we're kinda passing as we go through, but I did need to unblock that one. Sweet, you'll see the number that says todo here, right? That's a great way to get a sanity check on whether or not you are actually whittling down that list.

And that is different than them being skipped per se, only in a nomenclature way, right? It's the same thing, but it's a way for you to keep track. Cool, and now we get a little more detailed here, which is we should be able to add something, add a status to the board and then see this there as well.

And so we could say something like, we'll get rid of this for a second. And we'll say board.addStatus and we'll say, what are we looking for? Verifying, pick whatever you want. And then we can expect that if we call that addStatus, then it should be in there. And you'll see that one of the things that I'm doing here, if you don't spell it right cuz you're talking and coding at the same time, you have issues as well.

It's kinda common pattern for tests, which is kinda a setup. The exercise, and then we can kind of verify, right? And so you kinda break it into these three phases as well. Can you use a before each to create this board? You can. I choose not to because, you know what I don't need in my tests?

Things to be abstracted and clever. I just wanna see what's going on. When my test is failing, I'm not in the mood to like scroll up and look to see where that thing was populated. You think everything's gonna reuse the same object and then you find out very quickly that now you actually need different ones in some cases.

Cool, and so we can go ahead and we can also remove one as well. And this will be the same basic idea in this case, right? So we can say board.removeStatus and we'll say Backlog, and then we should expect. Now, this one's an interesting one cuz I might choose to do this one in two steps, right?

Cuz if I remove one that's not there, it could still pass, right, if I mess this up. So this one, I might choose to write two sets of expectations. One that it's there and then that it's not, Mark?
>> If addStatus was an async call to the server, would the test look similar to this?

>> I'm gonna answer that question now and we'll talk about this and this will kinda help us skip a section later. So the question was, if addStatus was asynchronous, what would happen, right? And it all depends right, in this particular test, it would fail because we run a function from top to bottom in JavaScript before we check the event loop.

So here we would add it and then we would have thought that was in there and it wouldn't be in there yet, right? That said, if for instance, whereas this gets a little bit more problematic would be this test here, right, which is, it's a lot harder. If it wasn't in there, you get to some weird edge cases, right, because this wouldn't have happened yet.

If things do return a promise, which is very easy in the modern era to simulate, boom, now it's an asynchronous function that returns a promise. RemoveStatus, And we can say not toContain Backlog. Cool, and then so this one passes. Did I save that file? Async move statuses. Cuz it's modifying the object.

If I was waiting for a return value, this wouldn't happen. Because it's a class, it just happens to work. But you could see a word where it's like, if there's a function return value, we wouldn't have it yet. And this is where it can become somewhat tricky. If you need to deal with the fact that you need the actual function to get called and fully resolved first, that is relatively straightforward.

You can just make it an async function and await it, right? And so, it depends, the problem here is that if I misspelled this one, It also still passes even though it shouldn't, right? And this is where you might choose to make a test fail to make sure you're getting what you want.

If you ever have any suspicion, particularly when you're dealing with asynchronous things, because again, the absence of a failure is technically passing, right? And this can bite you in fun and unexpected ways.
>> Because maybe Backlog F was never there to begin with?
>> Yeah, and so it's passing cuz it works, you know what I mean?

It's true, and this is why sometimes you would want to either make your tests fail or to say toContain or something along those lines to at least get the feedback loop that you're not-
>> To expect in that same plan.
>> Yeah, I might expect that it's in there first and then expect that it's not.

And there are other ways you can structure this as well, right? Here, I've made a, this would catch it, right, cuz it's not in there. However, still subtly broken but passing, right? And my recommendation for how to solve that would be to just take yourself out of the equation as much as you can and just store it in a variable, which is cool.

We have a status because spelling a variable name wrong will blow up in your face. We'll expect that it's in there, remove it, and then expect that that same one is not in there. And this is going to make you a lot happier as a person because now your test will not accidently pass because of some reason in this case, yeah.

>> It's all the same one time so we can assume you'd almost wanna set that more higher up globally if you're gonna use it for multiple tests in the same suite?
>> Maybe, maybe, right? Could I do it before each or even if it was, and then reset it?

Yes, and we'll see how to do that, but then it's tricky because now, I'm not very smart. And if I don't see it in my very large font editor right now, it becomes tricky as I have to scroll up to verify the thing is what I wanna have. For a lot of times, like the whole do not repeat yourself kinda thing, I think is true for the actual implementation of writing software.

Sometimes for my test, I would rather repeat myself. It have to be incredibly explicit, particularly when I'm refactoring code and I didn't expect a test to break. My patience is way lower than if I'm writing a feature for the very first time. And so in those cases a lot of times, I will choose to repeat myself.

Cuz then all of a sudden, you set one, but you need this one to actually be a little bit different and it just gets confusing. But then there are some times where it's, if it is a lot of work each time, then yeah, you can do it before each or something along those lines.

It is kind of defer to your judgment, in this case.

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