Check out a free preview of the full JavaScript Testing Practices and Principles course

The "Unit Test Demo" Lesson is part of the full, JavaScript Testing Practices and Principles course featured in this preview video. Here's what you'd learn in this lesson:

Kent demonstrates how to write a pure function with Jest and introduces test-driven development.


Transcript from the "Unit Test Demo" Lesson

>> Kent C Dodds: Let's go ahead and I'll demo how to write a unit test here. And then I'll set you free on this userToJSON and we'll take a break. Sound good? Great.
>> Kent C Dodds: So when you're running unit tests or when you're running tests at all, you wanna take a step back and think of this as whatever it is that you're testing, think of it from the perception of a user of that software.

So, like in this case, an end user isn't going to be using the isPasswordAllowed function explicitly, though. That's more like an end-to-end test type of experience. But a user of this software would be some other software that's using this, like in the password validation phase of user registration or something.

And so as a developer who will be using this software, I want to be able to call this password field with the password that the user gave me and check whether or not that password is allowed. So in this example, it's a pretty simple, straightforward function. But that's a really important principle of testing in general, is that the more that your tests resemble the way that your software is used, the more confidence those tests can give you.

And so in anything that you're testing, take a step back and think, how would the user of this software use this? And that's how you should test it. And then, how would the user of this software verify that this software is working properly? So in our case, it's pretty simple, especially with unit tests of pure functions like these.

It's normally quite easy to write these tests. So we're using ES modules in here. So we can import isPasswordAllowed from, we're one directory down, so we're gonna go two directories up. And you go ahead and type that out too if you want to. You'll be importing the userToJSON, so you'll want to have that in yours.

So, how do we verify that this thing is working properly? Well, we can call isPasswordAllowed with just a string of nothing. And I would expect that to be false, so that password shouldn't be allowed. What other situations? Well, what if we just have, let's do just f, and that should also be false.

We could also just do numbers, that's long enough, but that should also be false. And then we'll do something that is a good and valid password. So we'll do whatever, some stuff, and that should be true, okay? So I'll save that and because we're in launch mode, our test will automatically rerun and everything's passing.

Now, this is a very important step that you should not skip when you're writing tests, so listen up. It very well could be that your test, your assertions are not running at all. Especially when we get to asynchronous test, a test that rely on things happening asynchronously. It's pretty common to wind up in a situation where your assertions are never running.

So a way to simulate that is like if (true) { return }. So now my assertion is not running. But there's no indication in my output that those assertions are not running properly. So there are a couple things you could do. There is actually in the, where'd the jest expect, you can have assertions.

>> Kent C Dodds: And you can specify how many assertions should run. I really don't like doing that. Cuz it's really annoying to have to increment that number every time I add more assertions. But we could do it that way, we could say, okay, let's put that thing back at the very top.

We'll say we should have four assertions. And then it'll say, hey, your assertions didn't run. So that's one way you could accomplish this. And there's nothing wrong with that. It just annoys me when I add another assertion, I have to increment that number. So what I like to do when I'm running the test is I like to negate my assertions.

Or, actually, even better than that is to break the code. But here, we'll first negate the assertion. Okay, great, so the assertion is at least running. It can fail, it's possible for this thing to fail, and so that's good. But like I said, what you can do even one better is break the source code that it's testing.

The reason why breaking the source code it's testing is better is because then you not only make sure that your assertion is running, but you also make sure that your test is running the code you think it is and that it's actually testing what you think it should be testing.

So this is actually one of the reasons why TDD is so nice is because test-driven development, is because, if you write the test first and then you write the source code to make it pass, before you've even written the source code, if that test fails, then you know you're testing the right thing.

If you write the source code first and then write the test afterward, then you can't verify that your changes have actually fixed the bug that you're intending to fix. So you wanna reveal the bug first. And we'll talk about that more and have an exercise for that later.

Okay, so we verified that our test can fail and that it's testing the thing we expect that it's testing, so we should be good to go with this.
>> Kent C Dodds: Right, any questions about unit testing? Yes?
>> Speaker 2: Do you typically put a bunch of expect statements inside one test wrapper?

>> Kent C Dodds: Yeah, that's a great question, and a pretty common one. So in times past, the recommended way to test was only one assertion per test. A big part of the reason for that was because if one of these fails, like let's say this is true, the error messages weren't super great.

It would just say, this title of this test failed. And it couldn't tell you exactly where that failed or what exactly happened. And so if you have 30 assertions in here, you're like, okay, which assertion broke? So this is why having a testing framework that has really good error messages is so critical is because it allows you to do something that actually makes a fair bit more sense without sacrificing knowing exactly which assertion failed.

So I have no problem having multiple assertions. We'll have several examples in here, especially when we get to integration tests, where we have a whole bunch of assertions. We're doing a whole flow of things, asserting as we go. And that's much better than the alternative, which is we put up this setup in this before each function and we have 30 tests that are going through.

And just have one test that goes through that flow. It'll be faster and more reliable. So that's an excellent question, I'm glad you asked it. After you do your exercise, I'm gonna refactor this here. I'll add a TODO so I don't forget, refactor. Because there's another thing that we could do to improve this slightly as well, to make the error messages better and to make it easier to maintain.

But for now, this is the way we're gonna be doing this. Thanks for asking that, that was a great question.

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