By: Daniel Wanja
HttpTestingController class that comes with the
@angular/common/http/testing package is small and mighty, simple and powerful. It provides only the following four methods that can be used to test a large variety of scenarios.
I this article we will explore the usage of these methods showing you how you can test your services and components with great flexibility.
You can find all the examples on Github.
What are we testing?
Whether you use a service from a component or you are building out your remote service layer you want to make sure of two things. First that the correct requests are issues, secondly that you service or component behaves correctly based on a variety of responses. With the
HttpTestingController it is quite easy… let’s check it out.
So in this example, we are testing the
BlogPostsService class which is a simple RESTful client allowing to list, create, update and delete blog posts. So basically issuing the following requests:
You can view the code of the
BlogPostService class here
It uses Angular’s new
common/http/HttpClient. Let’s look at one of the methods the service implements; the others follow a similar pattern.
To retrieve a specific blog post, we issue a
get request then map the response to convert some dates. Notice we don’t call
subscribe here so the class that uses the
BlogPostsService can control when the call is triggered, and furthermore can chain other
RxJS operators as needed.
So let’s check how we can test this service.
Let’s go – The simplest case
In its simplest form you want to make sure that the service issues a call for a given URL:
In this case, it has to match the URL, and the URL should not have any query parameters. Also be sure to call
subscribe on the service otherwise no requests fires. Perfect it works. The
verify method is there to ensure that the service issues no unexpected calls.
Expect one request
If you want to have a bit more control over what type of requests you expect you can pass a function instead of a string to the
requestOne method as follows:
HttpRequest and we will explore this class more in-depth in a little bit.
If you only want to check an expected URL and method you can use the following form:
But wait, there is way more, you can match URL parameters, and ensure that the proper content is passed onto the request, and of course, we’ll explore how to return a variety of responses.
Let’s step back just a bit and see what happens behind the scenes. When your application is running in the browser, not in test mode, and the service invokes
http.get then an actual call to a server is issued, and a little bit later a response comes back asynchronously. However, when we write unit tests we don’t want to use a real server, we want to use a mock server and be able to return a variety of responses to ensure your application behaves correctly in all circumstances. To this end, Angular provides a mocking service that enables us to inspect the requests and allows your to specify the response returned for each of the calls.
So let’s see how we setup our test harness and mock server. First import the
Now you need to import the
HttpClientTestingModule module. When you import the testing module, it imports for you the normal
HttpClientModule which enables the HttpClient you use in your service, and it also provides
HttpBackend as an instance of
HttpClientTestingBackend, which is the mock backend you want. Wow, that was a mouth full! So mainly do the following:
By importing the
HttpClientTestingModule, your backend is now the mock we use in all our examples. This configuration injects the proper mock backend into all classes, components, service, whether your own or third-party ones as long they use the new
Now when running your test whenever the service calls
http.get, you have access to all the requests and you can provide individual responses to these requests.
Let’s get back to testing some more.
expectOne returns a TestRequest
The call to
expectOne returns an instance of
TestRequest allowing you, as we’ll show in a bit, to emulate responses from the server. You can additionally check if the service canceled the request.
In the following example, you can see that we can access the request method to ensure that it is the correct one.
match instead of expectOne
If you want to test a series of requests you can use
match instead of
expectOne. This approach can be the case of a complex service method that chains multiples call.
In the example below, we issue two calls directly in the test, but you can use this approach to test service methods that would issue multiple call, like for example using
You’ll pass a matcher function to the
match method, and
match will collect all matching requests. If you return
true, you will get all the issued requests so far. Note that
match doesn’t fail the spec if there are no matches, it’s just used to collect a call, you can write your expectations once you grabbed it.
match different requests
If for some reasons you wanted to segregate different calls you can even have multiple matches as shown below.
We now have two lists of calls. This example demonstrates that you have lots of flexibility using the
What about URLs with params?
Glad you asked.
expectOne expects an exact URL that doesn’t contain URL parameters. If you expect
/posts it would not match
/posts?page=1. If you need that level of granularity, for example, you want to ensure you pagination logic issues the right parameters, then you can use match in conjunction with
request.urlWithParams. Et voilà!
And a few more useful attributes
The expect match function signature is the following:
HttpRequest class has more useful parameters you can use when you write your tests. Here is a more extensive use
Requests and responses
Making sure your application issues the proper requests is important, but mostly your unit tests will make sure your app knows how to deal with actual server responses. When unit testing you are testing the various layers of your application, and the goal here is not to “test” the server, we reserve that for integration testing. The goal is to exhaustively test that your component can deal with a representative list of scenarios, for example, a spec that returns an empty response, a response with one record, another with many records, a server or network error. Not only is writing unit tests is faster than in-browser debugging with the usual “save, reload the browser, click here and there” cycle, particularly if you use tools like wallaby.js or do focused testing (by using
fdescribe instead of
An easy way to get the JSON for a server response is to use Postman, Curl, or the Chrome console to copy the real JSON returned by a specific request. Then you can use that JSON as the response content from your test. You can turn JSON into a Typescript file, or use the JSON directly in your spec if the payload is not too large. I mostly have a fixture folder with many files named something like
get_policy.json.ts that declare a constant that is the payload. You may also want to have integration tests that verify parts of your application against a real server, but this is outside the scope of this article.
One aspect to consider when writing test is to test actual code and not just the framework. For example, in a service spec, if you return a response to an
http.get request that does not transform the response in any way, then you didn’t test anything other than the response you provided in the first place, which would be pointless. If on the other hand, your service does data transformation, then your test has value and allows to ensure that your code works properly. For component testing that involve services, I see many developers mock the service calls. I find it as efficient to simply
flush the expected response using our mock backend (
HttpTestingController) and ensure that the component behaves as expected.
Now let’s look at a few examples.
Flushing a response
So when calling
match, our mock backend returns an instance of
TestRequest that has the following methods
These methods allow emulating a variety of results. In the example below, the call to
getAll() returns an array of blog posts and we ensure that the expected length was returned and most importantly that our date transformation code works.
Creating a post
Similar than the previous scenario, the call to
backend.expectOne returns an instance of
TestRequest which we use to ensure that the request method is a
POST then we flush the response back to the service. Inside the subscribe block of the service call we use the
jasmine.objectContaining utility method that allows checking partial objects, which is interesting when you receive a large payload.
When thing go wrong – responding to server errors
When flushing you can also add a specific status, for example, a Ruby on Rails server often return 422 when there is a validation error letting us know that the user provided some invalid data.
In addition to passing a status to the
flush method, you can also simply call the
Did you know… HttpClient Observable clean up after themselves
The following example shows that a request is canceled after you flush its response.
When you look at the actual implementation of
HttpXhrBackend you’ll see that the code effectively completes the observable upon receiving a successful response:https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts#L217
The testing framework emulates this behavior, check out the
TestRequest source code:https://github.com/angular/angular/blob/master/packages/common/http/testing/src/request.ts#L66
Reactive Style switchMap example
One coding practice I see in many projects, often spearheaded by (NgRx)[https://ngrx.github.io/], is to wrap your call with a chain of Observables. One advantage is, for example, using the
switchMap operator which allows you to cancel requests that haven’t returned yet. A good example where this is useful is a case when having multiple calls where, when you select a master record some details information is retrieved remotely. Due to the asynchronous nature of the calls, there is no guarantee that the calls return in the same order you service issues them. If you don’t cancel the detail calls in that scenario and a hyper-caffeinated user selects many master records before even the first details information returns your data will most likely be out of sync and show the wrong detail information. Canceling previous detail calls when new a new master call is issued ensures that the master and detail information stays in sync.
In this last example, the service uses a
rxjs/Subject to which a component can subscribe. This subscription stays open until you unsubscribe from it, and you can use this subscription to trigger multiple requests. Each request will close, but not this subscription.
To allow canceling previous requests, we define the
setupGetRequestSubject method that returns a Subject with the
switchMap operator applied. As the documentation of
switchMap mentions, this operator can cancel in-flight network requests! Think of it as “switching to a new observable.”. Exactly what you want.
So we have one active observable that is triggered every time the
getViaSubject method is invoked.
That Subject is long-lived and triggers itself the
http.get calls which are short-lived. The advantage is now we can wrap the HTTP calls with
switchMap and ensuring that if we have multiple calls in sequence, only the last one is taken into account.
In the test below you can confirm that this works. Two calls are issued before a response comes back, and the second call cancels the first one. Check, that worked. Although the call is canceled our subscription is still alive, and we can issue further calls.
I agree this example was a bit contrived, but hopefully, it shows how you can use Angular’s excellent testing framework to explore how your application issues requests and uses Observables. The
@angular/common/http/testing library is your indispensable friend in this scenario.
- The Angular spec describing the HttpClient behavior is a good read for understanding all the aspects of the HttpClient class. Might be good material for an article. https://github.com/angular/angular/blob/master/packages/common/http/test/client_spec.ts#L19
- This code shown in this article can be found on Githubhttps://github.com/angularityio/playground/blob/master/apps/example-001-httpclient-testing/src/app/blog-posts.service.spec.ts
I hope you enjoyed this article. See you at ng-conf!
President and Coder at Angularity.io