In my previous article, I defined the different types of testing you will typically see in a (well-tested) Angular application. The article describes each testing strategy at a high level; however, several people have asked me to go deeper. I certainly see the need for it. For example, many people have felt their component tests were unit tests for years. So I decided to compare and contrast these types of tests in a 4 part series about unit, component, integration, and end-to-end testing.
What is Unit Testing?
Unit Testing in Angular is generally done with Jasmine + Karma or Jest. I’m not going to get into the whole “Arrange-Act-Assert” paradigm. We’ve all heard it, but if you haven’t and you’re looking to learn that pattern, this isn’t the document for you. I want to take a look at the definition. Unit testing has been adequately defined all over Medium and the internet. Wikipedia defines it as:
In computer programming, unit testing is a software testing method by which individual units of source code — sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures — are tested to determine whether they are fit for use.
And at first glance, you may think, “Well, a component is a piece of code,” or that a component is technically a “building block” of a larger piece of code. So any testing for a component could rightfully be a unit test, right?
https://medium.com/media/1afb7dd1f2cb7305ea3ce397f49bfcca/href
How does one unit test a component?
To adequately compare and contrast Unit and Component Testing, we need to look specifically at a component class. So, let’s take a look at an example:
https://medium.com/media/7e244c3ad9560f290df954b334519aa8/href
Here, we have a basic header component with a single input that takes a string to display on the DOM. It doesn’t perform any functionality, so if you created this component with the CLI, you’d generate this spec file:
https://medium.com/media/668ee42d202fbece823686ec837655b8/href
The sole spec just determines whether or not the component compiles without self-destructing. Technically, there is no functionality being tested and I would not entertain an argument from anyone trying to convince me that this is a unit test. I guess you could argue that you’re testing the initialization of the component class, but I would roll my eyes at you. This is a fine way for the schematic to test that the component was created without any issues.
Where things get interesting is if we were to pass a name instead. Let’s assume we have an object that is passed in from a consuming component and we want to display the data in a particular way:
interface User {
firstName: string;
lastName: string;
}
You may have done this in the past:
template: `<h1>Hello, {{ displayName }}</h1>`…ngOnInit(): void {
this.displayName = `${user?.firstName} ${user?.lastName}`;
}
Now we have some functionality that we can test!
https://medium.com/media/40b26396666dc403c561d4baa475ce71/href
We could test that displayName could be displayed with only a “firstName” or a “lastName,” and we could pass nulls; however, what makes Angular so great is that it gives us the ability to do what we just did outside of our component class. For example, template modifying code doesn’t actually belong in our component class. This is an intriguing concept that many developers get wrong, and it causes testing to be more complicated than it needs to be.
Functional Programming and Unit Testing go hand-in-hand. And there is nothing in the Angular ecosystem as functional as pure pipes!
https://medium.com/media/e84751a4fefaefc302ef488ee73bcf32/hrefhttps://medium.com/media/3c7e8e27205c779536a7ec4fa192c957/href
This is just a single example of ways to extract functionality from your component, but the goal is to get rid of as much functionality there and move it into pipes, directives, and services. Another example would be if you had a form or an action that requires a button click to perform some functionality. Calling your service directly from the DOM instead of creating a pass-through from your component class to your service class lets you not worry about testing code that calls a service.
So I will never unit test a component?
In the past, I would look at code snippets of components with just 20 lines of typescript and constantly think, “Well, that’s just not realistic in an enterprise codebase.” If you feel that way, it will become a self-fulfilling prophecy. That being said: “Yes… Hopefully!”
If your components are designed correctly, and you’re properly leveraging pipes, directives, and services, you shouldn’t need to write component unit tests.
What will I unit test then?
The other classes I keep mentioning! “Pipes, directives, and services.” Anything described as “business logic” or “utility” should return a value. With that being said, I think unit testing HTTP services is pointless…
https://medium.com/media/0c006e60488f44c6582af39ef2afb598/href
Yeah… I’ve never been a fan of testing HTTP requests. I won’t judge you or call it out in a PR if you do it, but generally, I’m more interested in what happens with the data provided to the component. I’ll talk more about this in part 3 or 4, but to drive that point home further, so you understand where I’m coming from: If I have a service call, there is a “happy path” that occurs after it or an “unhappy path.” I care about how my application behaves after either of those scenarios. The functionality can be tested, but you’re not going to get much value knowing that I call a mocked toast service or throw an error to a component stub (my service is isolated in a unit test, remember?). Testing that my service makes a single request when I explicitly call HttpClient.get() is my definition of just writing unnecessary code.
What if I have a minimum coverage needed?
Nothing I’ve said up to this point should affect your code-coverage percentage. In fact, you should have far less tests if you’ve designed your components correctly and your business logic and utilities are placed in files outside of your component.
Summary
This first part of the series isn’t much of a comparison article as it lays down the foundation for how you can test your functionality, get huge benefits from unit tests, and still introduce or appropriately leverage component testing. If you take anything away from this, I hope it’s the fact that unit tests test functionality, not the DOM. Also, if you noticed that I never brought up fixture or TestBed in this article, it’s intentional :).
Was this already your understanding of Unit Testing in Angular? Do you have other thoughts? Let me know what you think!
Angular Testing: Unit Testing was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.