An Intro to Software Testing: End-to-End vs. Unit Testing

In this lesson, we introduce the concept and benefits of unit testing in web applications.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two with a single-time purchase.

Thumbnail for the \newline course TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two
  • [00:00 - 00:21] In this lesson, we'll go through a brief introduction to unit testing in general, and we'll keep it brief primarily for the folks who may be new to the concept or who are unaware of what testing or unit testing is within the context of web applications. So the very first question is, why do we need to even test our apps?

    [00:22 - 00:38] Testing does a lot of beneficial things, but as a quick summary, testing can help reveal bugs before they appear. They instill confidence in a web application, and they make it easy to onboard new developers onto an existing codebase.

    [00:39 - 00:58] As an upfront investment, testing often pays dividends over the lifetime of a system. Now, a lot of these benefits, in my opinion, is mostly understood when one actually begins to work in tests or with tests within a larger-scale application.

    [00:59 - 01:17] Now, there's a lot of comments that have come across as of late, from different people within the community. Some supporting the fact that testing is very important, and there's other people who've actually mentioned the fact that spending a significant amount of time focusing on tests is time taken away from actually working within features or building an app.

    [01:18 - 01:57] My opinion, when one works on a very simple personal or hobby project, I don't usually do any of the tests I need to do, but the moment I start building an application, whether it's for a customer, a client, when I have other developers working with me, and when the robustness of the application is of utmost importance, this is when testing takes a critical role, and usually within these cases, code wouldn't usually be pushed onto production unless tests are actually built for it. And the last thing I'll mention here, which I think is a very useful piece of knowledge that I've picked up over time , is testing isn't often a hindrance to building an app.

    [01:58 - 02:25] Instead, testing can actually make it easier to build an app over time. There's a lot of different methodologies that exist today, whether it's test-driven developments or some other framework of work that can be done, but the main concept around a lot of these pieces of knowledge is when you think of building an app that needs robust unit tests, or tests in general, you start to structure your app in such a way, and your code in such a way that the test can be written in a neat manner.

    [02:26 - 02:30] So it sort of pays both, right? The better you write your code, the better your tests are.

    [02:31 - 02:41] The better your tests are, the better your code is. Within web applications, there's often two big buckets when it comes to testing .

    [02:42 - 02:56] There's unit testing and end-to-end testing. Unit testing is a confined approach that involves isolating each part of an application and testing it in isolation , or in other words, testing it as a unit of its own.

    [02:57 - 03:08] These tests are provided a given input, and an output is often evaluated to make sure it matches expectations. End-to-end testing is often a little different.

    [03:09 - 03:27] End-to-end testing is a top-down approach where these tests are written to determine whether an app has been built appropriately from start to finish, or in other words, from end-to-end. So end-to-end tests are often written as though a user is moving through an application.

    [03:28 - 03:47] And in many other times, end-to-end tests are also often labeled as integration tests, since in these cases multiple modules or parts of a software are often tested together. As opposed to unit testing, where in unit testing we test each specific piece or unit in isolation.

    [03:48 - 04:28] In this module, we'll focus purely on unit testing, and we're not going to talk about how to do end-to-end testing or the many different ways to do so. Now, let's go through a very high-level example on how we want to think about our unit tests, and let's assume that we were working on something that was some sort of calculator, and I'm keeping this brief or vague because it doesn't matter if this is a component or a function or a module. The concept here is assumed we just have some code that resembles what a calculator is supposed to do, and now let's state that we actually want to write tests for it.

    [04:29 - 04:47] Since we're creating a calculator in this example, we'll assume that this calculator does all the arithmetic functionality that we expect. So 2 plus 2, we will expect the value of 4. 5 minus 3, we would expect the value of 2, or 4 times 4, you would expect the value of 16.

    [04:48 - 05:47] And our unit tests will look something like this. Now, we're not going to talk about the tools or the libraries that can help us create this structure of our tests, and we'll do so in the next lesson. But within the world of JavaScript and all the different testing libraries that exist, you would often notice that a lot of these testing frameworks allow us to create our test with describe and it blocks. Describe blocks or functions help segment each logical unit of tests. So here, we're saying describe the calculator. So we want to run tests for this calculator, component, module, whatever. The it blocks or it functions would be where we actually specify each test, or in other words, each expectation we'd want to assert for every test. So for the example of this calculator, we may write a specific test as it should sum 2 and 2 to 4.

    [05:48 - 06:54] Or in other words, it subtracts 5 and 3 to 2. Now, there is no restriction for how we want to sort of create our describe and it blocks. For example, another way someone can do this is they can have a describe block within the calculator describe just for the sum function. And we can say for this sum function for this calculator, it should have a specific test that says sums 2 and 2 to 4. And you can perhaps create another describe block for some other functionality that needs to be tested like the subtracts functionality. And in these cases, for example, one can now group multiple tests for each specific describe block that's different from another. There is no specific restriction or pattern one needs to follow. It's completely dependent on how you want to do so. The key takeaway that I often try to think about is one will need to sort of just create this test in a manner where someone else or another developer can read it and fully understand what is happening. So this, for example, in my opinion, is pretty readable.

    [06:55 - 07:45] It tells me that, okay, we're testing some calculator, we're testing some functionality in this calculator. And this is the summing functionality. And there's a test that contributes to this particular functionality. And similar, likewise here. Now that we have an idea on how our tests can be structured, the actual implementation to writing our tests could look something like this. Now this is pseudocode. This isn't real. But this actually could resemble some sort of functionality to be used to test some calculator module or unit. So for example , if you look at this test up here, the very beginning what we do is we arrange our tests. So we sort of prepare what we need to actually conduct our tests. So in this instance, we would actually import or use the actual calculator module that exists in our code. We wouldn't install or create another thing.

    [07:46 - 11:44] We'll actually say this is the calculator file or module we've created. Let's bring it into our tests just so we can make sure it does what we expected to do. After we arrange our tests, and in this particular case, it's straightforward. We just simply create an object that resembles this module sorts. We then actually write our assertion. But before we do our assertion, we actually make the act in our test. So we act on what we want to do. Since in this particular test, we want to test that the calculator can sum two and two to four, we'll say in this sum function within the calculator, let's pass some two parameters that it expects that would do the addition for us. So we're acting in our test. And the last thing that we do is we do our assertion. We assert that what we've provided, what input we've provided is equal to the output we would expect. So here, we 're saying we expect that this sum function of this calculator module, when we provide the input of two and two, would equal the value of four. What is this expect function? And where is this two equal function? Where do they come from? These are unique functions that actually come from the testing framework or assertion library that'll be used. And we'll talk about what tools we'll use in the next lesson. But regardless of which tools you pick, they would often follow a very similar format to this. They may look a little different, but they will have these statements that can be used to run your assertions. And they have these helpers that allow you to specify what you want to assert, whether it's too equal to be true, to be undefined to be null, etc, etc, etc. And now once you understand how this test, for example, is prepared, the other one looks practically identical. The only difference is we provide different inputs. And we're testing another piece of functionality, we're testing some other function in our module. And we're testing it that is going to be equal to a different output that we would expect. So we would expect this subtract function when we provide the values of five and three to equal a value of two. Now I reiterated a few points over and over. I said in each of these tests, we're arranging what we want. We're doing the act or the actual functionality we want to run. And then we're specifying our assertion. Those three words are often combined together to follow a certain pattern that is often encouraged when people write tests. And this is the arrange act and assert pattern. Regardless of what tests that are often been written, and regardless of how these tests are structured, and there's many different ways one can create their framework or set it up differently, at some points, you will notice most likely that at somewhere they're arranging what they're looking for the tests, they're acting in what they're actually trying to do. And they're doing the assertion as well. A good practice that I tend to follow is I try to have as much as I can, these three steps be kept as isolated as possible within this particular test. So it's a lot more verbose and easier to read when I see right away that, Hey, I'm arranging what I'm looking for here. I'm acting, I'm doing what I need to do, and I'm asserting. Right. So this is a fantastic pattern to think about when you're either beginning with writing tests in the first place, or when you're going through other people's tests and you're trying to understand what happened. At some area, you can see where they 're arranging something, where they're acting somewhere, and where they're actually asserting. Okay, I 'll stop here. Though this was brief, I do hope it was helpful, at least for the people who might be completely new to the thought of testing or just unit testing, and why it's helpful or useful. In the next lesson, we'll pick up what we've done here, and we'll talk specifically about the tools we'll use in our client application.