All roads lead to [better code coverage].
This post refers to Angular versions 2.0+
Other Relevant Libraries:
Jasmine, Rxjs, Angular Material
By Jon Caris
Jon is a software engineer for CH Robinson in Minneapolis, MN. He’s been working with Angular since putting all of your controllers in one file was cool (no it wasn’t). In his spare time, he hangs with his wife and two boys and helps to curate ngDoc.io. Whatever time is left, he dedicates to golf.
So you’re doing some work in your team’s Angular repo. You add a method to a service and call it from a feature component. Everything looks good and functions, so you hastily create a pull request, kick back and await praise from your dev lead about how sweet your code is. Instead, you find that the build broke because the “[foo] service does not contain” the method you added. You know you added it to the service, so what gives? The likely answer is that the unit tests for your feature component have a reference to a different, “mocked” service in which you forgot to add the new method.
Whether practicing TDD is your idea of a good time or you write unit tests because the DevOps team said so, you should be mocking the dependencies of your units under test. In this post, I will cover some of the many ways that this can be accomplished for service and component dependencies.
This post assumes that you have practiced Angular development and have a basic understanding of the theory around, and basics of, unit testing. For a very complete guide to Angular testing, see this helpful section in the official docs: https://angular.io/guide/testing.
In traditional unit testing, the general idea is to isolate the class and methods under test. In doing so, we want to make sure that the outgoing interactions with other units, within the same class or a dependency thereof, are happening as expected. Ideally, we would do this without the need to worry about the different ways that dependencies might behave. Enter mocks. If we provide some shell functionality of our test unit’s dependencies, we can assert that we are talking to them as expected without the need to execute the “real” logic or worry about unexpected behavior.
This idea still holds true with Angular and is still achieved in the traditional way when testing a pipe or service. Components, on the other hand, are better tested with the help of Angular’s testing utilities. We still mock our component’s dependencies but are more interested in how the component behaves and is interacted with as a component, or an interactive piece of a larger ecosystem, user interface and all. In the following examples, we will demonstrate a few ways to mock dependencies.
Let’s start with a simple application. I won’t go into depth about how it was created here but you may find the code in this repo and the demo is live here. The app was built using the Angular CLI and Angular Material. It allows one to search GitHub for a user with an autocomplete control and select a user to view some details about him or her. Structurally speaking, there is one feature or parent component which holds all of the functionality. There are several child components and a couple of services – one for interfacing with the GitHub API and the other for maintaining the state of the main view. The usefulness of this app may leave something to be desired, but the important thing is that it will allow us to look at some mocking and testing patterns. Let’s dig in!
Testing the Parent Component
Our one and only feature component holds all of the functionality for our application. As you can see, there are a number of component dependencies here, as well as a service dependency. When testing these types of “feature” components, the pattern I have settled on is to get a handle on an instance of the component itself and test the functionality from there. There are a couple of different ways we can go about this.
Method 1: Mock all of the things
This is the tried and true method of mocking all dependencies. The idea is that anything our component under test depends on will be replaced with a test double, or mock, so its original functionality will not be executed. We’ll add all mock dependencies to the spec file and test our interaction with them in a controlled environment. Let’s take an inventory of all of the things we need to mock here.
Mocking all of these dependencies would look something like the following. Notice that Angular wants us to include any input bindings that we are bound to from the component under test, such as the “user” binding on the app-user component.
From here we can test the functionality of this component. One thing I may want to test here would be the “selectUser” method in our component. To do so, I would spyOn the mock UserSearchService’s “getUser” method. With this spy in place, we can now assert that our “getUser” method gets called as expected and with the correct argument.
Note that because our UserSearchService is mocked and does not have the HttpClient injected into it like its “real” counterpart, we can rest assured that there won’t be any ajax calls originating from our test suite. This method of testing all of the things will give even the most discerning of developers warm fuzzies. Let’s take a look at a slightly less wholesome method.
Method 2: Mock none of the things
Even with this small example, we had to mock four dependencies. One can certainly imagine how creating the necessary mocks for a large feature component could quickly become difficult to manage. Luckily, Angular provides alternatives to the mocking madness. Enter shallow testing.
Note our use of NO_ERRORS_SCHEMA in our configuration of the testing module here. This means that we don’t have to mock component dependencies of this component as Angular won’t yell at us anymore for our lack of doing so. This makes testing large feature components much less labor intensive. After all, the Angular documentation itself says “The previous setup declared the [component under test] and stubbed two other components for no reason other than to avoid a compiler error.” This seems like a pretty silly reason to add lines of code to a spec file.
But what about the service dependency?
So we rid ourselves of our component dependency mocking woes. Do we still have to mock that service dependency? The answer, maybe surprisingly, is no. In fact, if we keep the same code displayed in our previous example, but remove the UserSearchService mock and instead add the actual UserSearchService, as well as subsequent dependent services GitHubService, HttpClient and HttpHandler to the providers array of our testing module, nothing will change. How, you ask, are we not sending a request over the wire if we have the “real” service injected? The beauty of spies is that they actually replace the functionality of the original method unless we indicate otherwise. Meaning that as long as we remember to “spyOn” any methods that might be called as a result of our test suite, we’re good.
The downside here is that we might lose sleep over the fear of forgetting to “spyOn” a method in our service that results in an Ajax call over the wire. If you and your team are willing to take on that risk, great! You are set.
Yikes! That seems sketchy. Is there a middle ground?
Why yes there is. We can provide a mock HttpClient in these situations, which gets injected into our “real” service. That way, we can once again rest assured that there will be no runaway Ajax.
Another benefit of doing this is that we can spyOn.and.returnValue the get and post methods here to test the handling of a response by our component under test. Other methods on spies that are useful are .and.callFake and .and.callThrough. The callFake method allows us to replace the functionality of the mocked method with our own function, callThrough just lets the spied on function run.
Now that we have a good idea of how to efficiently mock the dependencies of our feature component, let’s take a look at testing one of those child components.
Testing a Child Component
One of the child components worth mocking is the UserComponent. In the spirit of testing this component’s interaction with its ecosystem as previously discussed, we will create a mock consumer of the component under test.
This allows us to test the bindings of our UserComponent from the perspective of a consumer, which makes the tests more useful in this case than just testing in isolation. Our UserComponent is pretty simple. Let’s test the user input binding.
Any other dependencies of a child component can be mocked using previously discussed methods.
Testing a Service
All I have to say here is that testing a service, pipe or interceptor in Angular is done the traditional way: mock your dependencies, inject them into the instance of your unit under test, and test the functionality from there. You may use some of the tricks discussed earlier for mocking services that make Ajax calls.
These are just a few of the ways to mock dependencies in Angular. My hope is that after reading this, you have a better understanding of how to test your Angular application. I also hope that I saved you some time or even a few keystrokes. Which methods have you and your team used to mock your dependencies or save time while testing? Feel free to check out the full repo and demo application in the links below. Be sure to have a look at Angular’s documentation for a closer look at unit testing your Angular application.