JavaScript Testing Practices and Principles

Test Factories & Colocating Tests Q&A

Kent C. Dodds

Kent C. Dodds

Professional Trainer
JavaScript Testing Practices and Principles

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

The "Test Factories & Colocating Tests Q&A" 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 fields a question that segues into test factories, describe blocks, colocating tests, Jest globals, other tools to use in addition to Jest, and other topics.


Transcript from the "Test Factories & Colocating Tests Q&A" Lesson

>> Kent C. Dodds: Yeah.
>> Speaker 2: One thing I was just noticing [COUGH] something we do, like say you have a test like this. And it might be that there's some dynamic aspect to this, and so we'd have a list of properties we are expecting and another list of properties we aren't.

And then have like a test factory, essentially, that sort of foreaches on those properties. And I have my Jest set up for some of our projects. It's very clear if something breaks, and it basically lists out each of the generated assertions from in the list, it gives me an x next to the one that failed.

And I did the same thing for this, and it's presenting differently, so is that kind of Jest configuration stuff or?
>> Kent C. Dodds: I'll have to look at specifically what things look like. But I'm glad that you brought this up because that's what we're gonna talk about next. So yeah, this is great.

So with pure functions, this works super, super well. So if you notice, all of this stuff right here, that's identical stuff. But if there was a slight difference and maybe that difference is important like isPasswordNotAllowed, then you might not actually notice that. Up until this point, they're still all totally identical.

And so it could be easy to miss these little key differences between these different insertions here. And so one thing that I like to do is exactly as David was talking about, create a test factory. And we'll talk about test object factories, the pattern itself, but when we're doing a pure function like this, creating a test factory is a really good approach.

And so that's what we're gonna do here. I'm actually gonna put this in a describe block. This is one place where a Jest or, Mocha supports this, Jest supports this, Jasmine is very common, but it's a way of grouping similar test together. And so here, we'll just test the or describe isPasswordAllowed.

And inside of here, I'm gonna make my allowedPasswords. And then I'll also make my disallowedPasswords. And then for each of the, actually here, let's just stick those in here. So we've got one allowedPassword. We could add more if we wanted to but I think we're good with what we've got, so that's a disallowedPassword.

Here's another one and here's another one. Okay, cool. So, with this, I'll save my allowedPasswords.forEach, and this is gonna be a password.
>> Kent C. Dodds: And we could do an it block here. I think in my solution, I do a test block, it and tester, a oasis of one another.

So however you like to read it but I'll say it should be allowed. And actually even better because we're generating these tests on the fly, I'll make this a template it( '${pwd} should be allowed', ()=> {}). That will make my error message a lot more descriptive. If there's an error then I know this password should be allowed, but it's not for some reason.

So then I'll just copy this assertion right here and we'll pass in a password. Expect that to be true and we'll do something very similar for the disallowedPasswords. Yes, question.
>> Speaker 3: Well, I was gonna say because there was a possibility of an empty string, I would actually wrap for the temporary.

>> Kent C. Dodds: Good idea. Yeah, good suggestion. Put quotes around here.
>> Kent C. Dodds: And then we'll have our disallowedPasswords, and we'll expect this to be false.
>> Kent C. Dodds: And that's our free factor. And then if one of these were to fail, like we'd say, I don't know, maybe the length has to be greater or we remove this entirely, there we go.

>> Kent C. Dodds: Then that's, sorry, I forgot to say, how do we make this thing fail? I don't know. [LAUGH]
>> Speaker 3: Set the length requirement to 20.
>> Kent C. Dodds: Yeah, there we go, thank you. Awesome. So now we can see isPasswordAllowed. This should be allowed but apparently something's broken to make it not.

We can look at the assertion or this code block here and actually in this case it's not all that useful. When you are doing generated test, the error message will be pretty generic. It's pointing at a part of code that doesn't give you any indication of okay, which one of these?

If we had a long list of a lot of passwords, which one of these is the one that's breaking. And that's why it's important to have the title contain information about the specific data. So that when you see the error title, it will always show that. So you're trading off some convenience for a little bit of like, you gotta do a little bit more work to make sure the error message is useful.

But at the end of the day, what's really nice about this is for a really big, like if this were a really complicated function. Now you just put a bunch of these and you say, okay, these are all passwords that are allowed. If we are going to now disallow some more, then we just add another entry to this.

And it's a lot easier to add to this test. Also coming into this test, you just read that variable and you're like okay, I understand what these things are all about. So that is that, I'll show you a more extreme example and then,
>> Kent C. Dodds: Yeah, sorry.
>> Speaker 4: Can you talk about why you chose to put it in a describe block in this case and when you might do that?

>> Kent C. Dodds: There isn't a whole lot of reason to be perfectly honest. I mean, I have this isPasswordAllowed block, and so it appears kind of in my error message. Alternatively, I could say isPasswordAllowed, like prefix these manually and then pull this out of the describe block and that would be just as well.

So I'm fine either way. Like I said, I normally don't really like describe blocks so maybe I prefer this. But there is actually something else that I might do if I'm gonna have several tests like this in my code base and that is use this jest-in-case. So if you are gonna be using Jest, sorry just taking a step back, this is tool specific stuff.

But I love this, it is awesome. So you have this idea of cases. So we would have just an array of our passwords strings and then this is the title and then your assertion. And then you can actually provide the name property and that will be used as part of the title for your output.

So yeah, I love this, I should've gotten it ready, cuz I just refactored some tests in PayPal that leveraged this pretty heavily in a really nice way. Pure functions, this works really, really well with. Another real world example of this is I have a project called the rtl-css-js.

Which takes some CSS and JS styles, just objects as CSS, and then converts that to the right-to-left conversion of that. Pretty common tool if you're doing internationalization, but this is for CSS and JS specifically. So the test for this, because it's a pure function and there's a ton of properties that we have to manage, that's sort of how we're doing this in this file.

So rather than having a test for each permutation of all these different things, we just have, here's our input and here's our output. And it makes it much like really straight forward. Okay, paddingLeft's gonna be paddingRight, paddingRight's gonna be paddingLeft. And so on and so forth gets kinda complicated.

And by doing things this way, you know that all of these tests don't have anything special or different about them like. This test is operating in the same way, the assertions are happening in the same way. The setup, everything's happening exactly the same. So adding to, making changes to this test base is a lot easier, and people do it all the time.

The ones here toward the bottom were added by a lot of different people who were just like, I had this special case where if I had a space, then things blew up. So they add a new test case for that and then they fix it.
>> Kent C. Dodds: Okay, cool, let me fix my off thing here to be 6, cuz who wants a password of 20 characters?

>> Kent C. Dodds: Okay, cool, so that is unit testing at its finest. Well, at its basic, yeah.
>> Speaker 5: Were you gonna talk about where you located your tests at?
>> Kent C. Dodds: Yeah.
>> Speaker 5: Do you want to do that after?
>> Kent C. Dodds: I'll do that now, if that's cool. So yeah, actually, David, do you want to just ask that so everybody knows what we're talking about?

>> Speaker 5: Yeah, so I was just looking at the project structure, and I noticed that you had all your tests in a folder's called __tests, located near the source. And so I was just curious of where that convention came from, and if you could talk about it?
>> Kent C. Dodds: Yeah, totally.

So this is a convention that just supports, it also supports if you want to, you could add a file right here called off.test or off.spec. It doesn't actually have to have any particular name, it doesn't have to be the same name as the file's testing or anything like that.

But there's a pattern by default that Jest will match. So there are a couple of things that Jest call out, I actually don't really care what you call it. You can do the dot test or dot spec or you could change the pattern entirely. But one thing that I would recommend is collocating your test.

I've actually got a blog post about this. What code comments can teach us about scaling a codebase. It's a terrible blog post name cuz it doesn't give you any idea of what this blog post is about. But this is exactly what it's about, it's about collocating your tests.

Where in one scenario and this, I saw this in a lot of projects, you would have your source directory and then your test directory and they are mirrors of each other. And then you come in here and you delete this file and you forget to delete the test or you add a file and you forget to add a test.

You move files and you forget to move the test, it just gets to be a total nightmare. I've done this in too many projects or I've entered too many projects that were like this and it was really annoying. Because the test and the source get out of sync.

And so by collocating these things, it makes it a lot easier not only to make sure that you're moving the test and keeping things in the right place. But also, if I'm making a change to this image file, I see the test right there, I'm, I should probably wait out of test for this change that I'm making.

So it just makes test have a much more relevant position in your projects so that you're constantly thinking about it. So yeah, collocating tests, really, really good idea. And the premise of the blog post is that [COUGH] we don't write code comments in a comments directory that's mapped to our search directory.

That doesn't make any sense. You collocate your comments, makes a lot of sense in the same way to collocate your tests, yes.
>> Speaker 6: I wanted to comment that might be funny or helpful. When we moved to Jest, I loved the collocation. It's fantastic for ergonomics when you're developing.

Make sure you don't accidentally ship your tests. I almost did that.
>> Kent C. Dodds: Yeah, yeah.
>> Speaker 6: I didn't have them ignored in my Webpack build.
>> Kent C. Dodds: That's a very wise-
>> Speaker 6: [CROSSTALK] snapshots.
>> Kent C. Dodds: Yeah, yeah, that's a wise idea. So if you're doing some sort of, especially if you're doing code splitting or dynamic imports or something like that, that's probably what happened.

Yeah, make sure that your Webpack isn't gonna bundle up your tests, and snapshots, and stuff, yeah.
>> Speaker 6: If that's a collocating test when it comes to integration tests cuz for example, we have tests that operate at the page level. And our UI projects will often have our source code laid out by page.

And so within there it's like, all right, that makes sense, have like an index spec that's doing integration testing of that page level. But then we have cases where we wanna test stuff that actually moves the crest pages and so on and it's always kind of the head scratcher like-

>> Kent C. Dodds: Yeah, where do I put this. Yeah, that's a great question. I'm glad you brought it up. So the principle, let me see if I can find the basic principle. Here we go. So place files as close to where they're relevant as possible. That's the principle. And so if it's a unit test that's gonna be a single file, you put it right next to it.

If it's an integration test that tests several units, but they're part of a single feature, then you can put it right there, in that the least common parent directory or something. But yeah, if it's an end-to-end or integration of several pages, then at some point, I stop caring about least common parent.

I'm just like okay, this is just gonna go at the root. And that's what we do in this project as well, if you check out the client-side test. Or those where you have some integration tests at the the root of the source directory cuz that's like the whole app.

And then the end-to-end tests are actually an entirely different directory because we have the end-to-end test are covering server end client so it's at the root. So it's a great question. Any other questions? Yeah?
>> Speaker 2: So I guess it's pretty common for test and describe and expect all of this be available, you don't have to import them or anything.

What's the deal with that, and why haven't we moved on?
>> Kent C. Dodds: [LAUGH] From globals?
>> Speaker 2: Yeah.
>> Kent C. Dodds: Yeah, so that's a good question. Lots of people have raised that concern and prefer other frameworks that require an import of some kind.
>> Speaker 2: Do I have the option to import help my IDE help?

>> Kent C. Dodds: Yeah, yeah. So for the IDE help, you can configure ESLint, or whatever it is that your IDE is working with, to treat those as globals, that's what I do. From a philosophical standpoint of these globals from these testing frameworks, I actually, after you've decided, okay, well, we're using this testing framework.

All of our tests are gonna need this. It doesn't make a whole lot, I used to use AVA and I loved that, I imported all the stuff. And but I was like, okay, every single test looks exactly the same, then why don't we just cut the craft and just assume that AVA is loaded, or whatever.

So I'm actually fine with the globals. The problem with globals, philosophically, is it makes your application hard to maintain. You gotta load this script and then this one, otherwise it depends on a global, and there's a mess. But we don't have any of those problems with tests, we're not modifying these describer it functions.

We're not doing any of that stuff, so I'm less concerned about the globals. As far as whether, specifically with Jest, whether you can make it be importable. I feel like there's a project that allows you to import these functions and yeah, you could check that out. But for me, I'm tired of importing stuff that I'm using in every test, so I'm fine with having those globals.

So I wouldn't make them myself though. If the framework didn't already do the global thing, I probably would just import them, cuz it feels weird to make them myself, I guess.
>> Speaker 2: Could I give some insight?
>> Kent C. Dodds: Yeah, sure.

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