Web App Testing & Tools

Refactoring Code for Unit Testing

Miško Hevery

Miško Hevery

Qwik Creator (Previously Angular)
Web App Testing & Tools

Check out a free preview of the full Web App Testing & Tools course

The "Refactoring Code for Unit Testing" Lesson is part of the full, Web App Testing & Tools course featured in this preview video. Here's what you'd learn in this lesson:

Miško explains asynchronous calls can be difficult to unit test when they are part of a more complex function. Refactoring the code and putting the asynchronous call into a separate component makes it easy for a unit test to call the code independently and ensure it's functioning properly.

Preview
Close

Transcript from the "Refactoring Code for Unit Testing" Lesson

[00:00:00]
>> So you said the testing is documentation or you had me at testing is documentation and tests do a lot of that. And what I was gonna add is that, one thing that is difficult to get out of tests or any sort of source code, right? You can look at the source code and be, I see what you're doing here, I understand.

[00:00:16]
Especially if the source code is written in a nice useful way, you can always deduce what the source code is doing, so documenting that is not necessarily that useful. But what is extremely useful, because it's extremely difficult to get out of your source code is, to answer the why question.

[00:00:31]
Why are you doing what you're doing in the source code, right? That is something that is extremely difficult to deduce. And having a good explanation in your source code on the top saying, why am I doing this, is super, super useful. Let's do some coding. And so we kind of spent a good hour kind of talking about a theory of why do the testing, on how the testing works, etc, but at the end of the day, it's all about coding, right?

[00:01:02]
And again, as I said earlier, humans are really good at, if I give you a bunch of examples, you will deduce the general principles out of them, right? If I just give you general principles, you're gonna have a hard time figuring out what does it mean in terms of code, right?

[00:01:20]
So we kind of gave you a theory, which is the general principles, but now we have to show you the actual examples of how to do this so that you can tie it back to these general principles that we kind of talked about earlier. So let's do that.

[00:01:32]
Now, there are lots of different kinds of testing, and we're gonna cover them in pieces. We're gonna start with unit testing, which will be just the lowest level to kinda the most obvious way of doing the test. Then we're gonna do some integration testing about how to put pieces together.

[00:01:52]
Component testing, which is important when it comes to testing your UI. And then finally end-to-end testing, which will actually do the end-to-end process for it. Okay, now inside of unit test, we can talk about many different things and I'm kinda gonna talk about all of them, all at once as we kinda build these things up.

[00:02:17]
But I want you to pay attention to the tools we use to do that, test-driven development, how we create marking. Especially pay attention to dependencies, because to me, I think dependencies are the key thing that makes tests possible, right? If you have a good dependencies between your different parts of the system, dependencies in the sense of, these dependencies can be mocked, then you're gonna have an easy time writing tests.

[00:02:46]
If on the other hand, all these dependencies are gonna be hard coded in without the ability to replace them, then it's gonna be very difficult to write a unit test. We're gonna talk about a little bit about coverage. And finally, maybe some benchmark testing. So that's the idea of all the different pieces.

[00:03:04]
So let's dive in and see where we're gonna get to. Before I go to the source code, I wanna show you, kinda give you the goal of today. So I have two applications here, I have this URL that goes into Github and fetches data from it, which fetches the name and the description.

[00:03:28]
The UI is not pretty, but we're gonna go and try to write tests for that. And for kinda a higher level tests, I managed to find some data of every accident locations in Chicago over many years. So these are 5,000 traffic accidents in Chicago. And what I find interesting is that actually paints you the map of Chicago.

[00:03:51]
And what I did is, I ran clustering algorithm on it, and I'm painting each particular cluster with different color, just kind of for fun to kind of explore. And so this is gonna be our goal, we're gonna get to where we're gonna build up, we're gonna write unit tests, we're gonna write higher level tests, we're gonna write component tests, end-to-end tests, and so on.

[00:04:15]
These are basically the two scenarios that we're gonna be talking about today so to kinda give you an overview of where we're going, right? Okay, so also, I have a repo, if you wanna follow along. It is mhevry/testing-fundamentals. In the repo, there are two branches. So there is the main branch which everything is fully solved with all the tests inside of it.

[00:04:49]
And there's a separate branch called no-test. And so that's the branch where we're gonna start to, and we're essentially going to try to recreate everything from the no-test branch to the main branch. Does it make sense? Okay, so let's start with this particular page. So there are two things in a URL, which is my username on GitHub and a project on GitHub called qwik.

[00:05:22]
And what I wanna do, is I wanna be able to fetch the repo name and the description out of the database, something pretty straightforward. So let's go back to the index.tsx, okay? I don't wanna go down the particular route of how this particular thing works, but there is a function in here called routeloader.

[00:05:42]
And this function's job is to get your username and repo and then download it from GitHub, right? So what it does is it creates a header, which is specific header, which we have over here. We can have the access key. If you have it, and maybe add it into the header.

[00:06:02]
And then we just do a fetch to a particular URL, and we parse the JSON and cast it to a particular type over here, and return this, right? Now, imagine you wanna test this. And so this is an example of where refactoring for testability comes in. The only kinda test that you can really write for this is an end-to-end test, because while this function is called by the framework, and I don't actually know how to kind of call this in here.

[00:06:35]
So a typical thing to do would be to refactor this to a new more testable function. So let me show you how to do that. So what we're gonna do is, we're gonna take the code from here, essentially, let me actually move this up. And we're gonna say return, we're gonna say const api.

[00:07:05]
I'm making stuff up now, so new=GithubApi. I'm gonna pass in token, and then I will say, hey, return await api getRepository for username and repo, right? This is kind of a useful API that you can think of that would be here. And now I need to go and create this.

[00:07:26]
So I'm gonna create an api.ts. I'm gonna call it GitHubApi. So let's just put it next to each other, so we can see both. Wow, it did a lot of stuff for me here. Is that correct? I don't know, let me just put whatever I have in my, I wanna move this type across.

[00:08:17]
Sorry, that's not the right location. And then I need to get the type here It becomes this oops, And then that would be the token, right? Okay, so all I've done now is I pulled this code out into a new file. And the reason why this is important is because string are undefined, so this needs to be string are undefined.

[00:09:03]
[COUGH] Is I pulled it out into a separate piece of code. And by pulling it out, now gives me freedom to be able to much easier test this particular code, right? This refactoring was a relatively simple refactoring, I just moved the function to a separate file so that now I don't have to figure out how to call it in this complicated way with those routeloader, instead I can call it directly inside of here.

[00:09:26]
And you can argue, and I think I would argue that this is actually cleaner in terms of the complexity because I'm now putting all the logic related to fetching the code into one location that I can now unit test. Does that make sense? And so if we did this correctly, nr dev, oops, if I run the code, it still shows me the descriptions, it's still successfully fetching the data.

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