By: Daniel Wanja
The new 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.
- expectOne
- expectNone
- match
- verify
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:
Here we use the JavaScript string matching function to check any requests that have the word “posts” in its URL and use a ‘GET’ method. The request is an instance of 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.
Configuration
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 HttpClientTestingModule
and HttpTestingController
.
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 @angular/common/http/HttpClient
.
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 forkJoin
.
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 match
method.
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.params
or request.urlWithParams
. Et voilà!
And a few more useful attributes
The expect match function signature is the following:
The 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 fit
or fdescribe
instead of it
and describe
).
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 expectOne
or match
, our mock backend returns an instance of TestRequest
that has the following methods
flush
error
event
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 error
method.
Did you know… HttpClient Observable clean up after themselves
The HttpClient completes it’s observable once the full body of the request has been returned or when an error occurs. So basically one request get’s only one response, and after this, your subscription is dead. This is different than a “reactive approach” where the subscription would stay open. I assume closing the request is more familiar for developers coming from a world full of (promises)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise]. Then again, closing the observable works pretty well, and you can still wrap and chain your remote calls as we’ll explore in the next section.
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.
Further reading:
- 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!
Daniel Wanja
President and Coder at Angularity.io
PS:
- Please reach out on twitter @danielwanja or by email daniel@angularity.io
- I’m sure I missed something obvious in this article, so please let me know how I can improve it.
- Last but not least, thanks to Brian for review this article.
Leave a Reply