This is the last tutorial for the Unit Test in Angular tutorial series. In this tutorial, we will go over how you may write unit tests for asynchronous jobs such as API calls. In modern web applications, it is very common that the Frontend needs to communicate and exchange data with the Backend. While we want to cover these features in our tests, we do not wish our Frontend to send out actual API calls to the Backend during testing. Instead, we want to verify that our application sends out correct data to the Backend and reacts correctly when the Backend responds. Angular provides a set of handy utilities that allows us to perform this kind of test. We will go through them in this tutorial.
Demo Application Introduction
For this tutorial, we have prepared a simple forum application that uses APIs provided by JSON Placeholder (a public API resource for developers to simulate API calls).
The forum application consists of three pages:
Home Page – List all posts
Single Post Page – Display the content of a single post. Can also modify or delete the post.
New Post Page – Allow users to create new or modify an existing post.
APIs used by this application:
- List all posts
- Get a single post
- Create a post
- Update a post
- Delete a post
All API calls for this application are defined in the PostService file.
When we write test for functions defined in services, we want to make sure the following points are covered in our tests:
- API URL
For each method, is the API being sent to the correct URL?
- HTTP Method
For each method, is the correct HTTP Method used?
- Parameters & Body
Does the application send out data in the correct format?
Prepare the TestBed
In order to test API calls without actually sending API to the Backend, Angular provides us the HttpClientTestingModule and HttpTestingController.
HttpClientTestingModule is a test specific module to substitute the normal HttpClientModule during test. It will give us access to HttpClient in the test environment.
On the other hand, HttpTestingController allows us to take full control of all requests made during tests.
With HttpTestingController we can:
- Capture all API calls that would’ve been sent out to the Backend without the HttpTestingController
- Flush requests with our mock data to simulate Backend responses
- Capture API requests and verify their method and body
With this idea in mind, this is the setup of our TestBed.
Capture API Call and Verify Its HTTP Type
Let’s start writing tests for the getAllPosts method.
In the test case above, we start by invoking the getAllPosts method and subscribe the result as how we would normally make API calls on line 37.
Once we make the API call, we can capture the request by using the expectOne method from the HttpTestingController (line 40). The HttpTestingController will test for us if indeed a request with the given URL is received and it will return a TestRequest object for us to perform more validations later on.
We can then test the HTTP Type of the request by accessing the TestRequest.request.method property. Keep in mind that the HTTP Type will return in all capital cases.
Test the HTTP Body
For a request that comes with additional data in the http body (POST/PATCH), we can also test them with the TestRequest object.
HTTP request body can be acquired from the TestRequest.request.body property. We can then compare if the http body matches with the data we passed to the service method.
When testing a component that uses services, we want to make sure the following points are covered in our tests.
- The correct service methods are called and data passed to them are correct when the corresponding component methods are invoked
- Components can handle successful API responses correctly
- Components can handle error API response correctly
It’s important to keep in mind that from the component’s perspective, we are not interested in how the service method is implemented. The service method is like a black box to us. We only want to focus on what data we need to pass to them (Input) and how we react when the API returns (Output). Services related tests should be covered in the services own’ TestSuites.
Prepare the TestBed
To test services in component tests, there are two ways to prepare the TestBed.
- Create a mock instance of the service and register it to the TestBed
This approach provides the benefits of no need to worry about if a request is not captured with the HttpTestingController in all tests.
- Inject the real service into the TestBed directly
This approach allows us to test the service request with HttpTestingController directly, but it also comes with a risk that you’ll need to manually make sure all tests involving API calls are guarded by the HttpTestingController.
We’ll go with the first approach and below is our TestBed setup for the component test.
Test Service Method Call
Above is the getPostDetail method from the SinglePostComponent. In the getPostDetail method, we make a call to the PostService.getSinglePost. We can verify this behaviour in our test easily using Spies we covered in another tutorial.
Sunny Case API Response Test
With sunny case API tests, we want to prepare mock data that’ll be returned by API and see if our component performs the after action correctly. (update form, navigate to another page, etc…)
Below is a method from the NewPostComponent that will be used in update mode. When API returns, we want to patch the form with the latest data returned by the server.
We can override response data by using the returnValue method from Spy easily and verify if the patchValue function is indeed invoked with the mock data we return from the API.
Error Case API Response Test
The simulation of error API response is a little bit thicker than the sunny case because Jasmine Spy cannot simulate HTTP status code. Luckily we can use HttpTestingController to achieve this task.
Above is the deletePost method from the SinglePostComponent. When the API returns an error, we want to log its HTTP status, HTTP status text, and the error response body to the console.
Recall that we injected the PostService to our test environment by creating a mock class, we need to override the API method we want to test, so that it will actually send out an API request to our HttpTestingController.
Since we are not interested in how the service method is implemented, we can prepare a dummy url to simulate the API call in the Arranging phase. (lne 382 ~ line 386).
In the Acting phase, we call the component’s deletePost method and expect to receive an incoming request to the dummy url at our HttpTestingController.
We prepare the error response body, HTTP status and HTTP status text that we will be using to simulate API error, and finally flush them (on line 396) to send the response back to the Frontend.
In the Asserting phase, we verify if the console.error is called with the dummy data we prepared for the API.
We can see our data is indeed passed back to the Frontend and being logged to the console.
Note. In tests that use HttpTestingController, you may call the HttpTestingController.verify method if there are other API calls that you are not expecting with HttpTestingController in your test. The verify method will make sure that all API calls end (including unmatched APIs) before terminating the test case.
Congratulations on reaching the last tutorial of the Unit Test in Angular series! We have covered many topics along the way. Now with this knowledge at hand, you should be able to write unit tests for more than 90% of the common scenarios. The complete listing of the forum application and their tests can be found at GitHub under the async-test directory.
There are a total number of 97 tests in preparation for this tutorial.