Angular Testing Snippets: the new HttpClient

Migrating your karma/jasmine unit tests to the new HttpClient

In previous stories, I wrote about faking HTTP traffic in Angular unit tests and testing services that connect to HTTP backends. Today, I’d like to show migrating the tests to the new HttpClient introduced with Angular v4.3. The story is divided into two parts: first, we will take a look at the basics of the new HttpClient and play around with it in some tests. In the second part, we will implement a service that talks to a HTTP backend. We will then verify the behaviour of our service by faking HTTP traffic in the unit tests.


The new HttpClient

Migration to Angular’s new HttpClient is super straight-forward for the most common use cases: HttpModule is superseded by HttpClientModule and Http by HttpClient. There’s one notable change in the API, however!

In the “old” Http, we used to work with Observable<Response> by default and had to convert response bodies on our own with statements like http.get('/foo/bar').map(res => res.json()). The “new” HttpClient now does response body conversion and, by default, it assumes a JSON response returning an Observable<Object>.

https://gist.github.com/dherges/a8f8ce8643010dd65ad3052842eded1e

Beside that, the new API has renamed a few classes: it’s now called a HttpResponse<T>, HttpRequest<T>, HttpHeaders, and HttpParams. Notice the type argument for the outgoing/incoming HTTP bodies. When sending a request, you now declare the expected response type (one of arraybuffer, blob, text, json) and the type of the response body will be either an ArrayBuffer or Blob or string. For JSON responses, you either work with a generic Object or pass an interface describing the structure of the JSON document. You will then work with an HttpResponse<MyInterface> and Observable<MyInterface> on the expected data structure. Especially for RPC-like APIs where entities and data structures are often specified and known in advance (“contract first”) this seems helpful.

For more advanced APIs — e.g., when working with custom content types like application/foo+json or when the client does not know the returned content type in advance — this could require a little bit more work. Since I don’t have an example that addresses this directly, I can’t say anything about the migration costs here.

The APIs of HttpHeaders and HttpParams haven’t changed too much compared to the “old” Angular Http module. I have found that migrating to them is merely a “find-and-replace” of the class names. But make sure that your code works well with the copy-on-write behaviour! The latter, HttpParams, can be used for both query string parameters as well as form-encoded response bodies.


Verifying and faking HTTP traffic

Now, let’s write some unit tests and see the HTTP testing API in action. First thing to notice is that the amount of boilerplate code for the test setup has significantly reduced. In the “old” Http API, we had to set up custom providers. Now, it’s as simple as importing both HttpClientModule and HttpClientTestingModule and we’re ready to go:

https://gist.github.com/dherges/3d2a376d5eb5f1129783b5be2e4ca876

We still have to use the async() and inject() test helpers since we’re dealing with asynchronous operations of the HttpClient module. Also, the syntax of the inject() helper is — in my view — a little bit too verbose and could be shortened.

The API of HttpTestingController is the successor of the MockBackend in the “old” Http module. Again, the amount of boilerplate has been greatly reduced and it has become an one-liner to verify for an expected HTTP request (ok, I’ve written the above example in super verbose multi lines but you can write it in just a single line anyway).

The API for matching requests is build around three methods:

  • expectOne(expr): expect exactly one request that matches
  • expectNone(expr): expect that no requests matches
  • match(expr): match the request but do not verify / assert

The last point is noteworthy. When expectOne() or expectNone() are not matched, it will cause the test to fail. In effect, both expect methods match HTTP requests and verify if the expectations were met. In contradiction, match() only matches but does not automatically verify — we’ll see later why that’s useful. If we want to verify with match(), we have to call verify() explicitly. Here are the code examples:

https://gist.github.com/dherges/2f70913a952d0d7e936b9aaeb3062651

Notice that the last three tests fail by intention. The test failures are:

As the last basic example let’s see how to fake HTTP responses. With all three methods — expectOne(), expectNone() and match() — it’s possible to respond with successful responses or errors (error responses or network errors) and even with progress events (e.g. an upload progress). Here’s the simple example that responds with a object literal (i.e. JSON response from the server) and expects something in the next subscriber:

https://gist.github.com/dherges/d109cce85eb203ce74a1f5d73a0abcc3

Testing HTTP-based services

Let’s now write a service that works over HTTP and then write a unit test for the service. As a simple example, let’s take a straight-forward form-based login. Given a username and password, it sends a form-encoded POST and returns a boolean flag indicating the login result based on the HTTP response code (200–299 are treated as success in this example). Here’s the implementation:

https://gist.github.com/dherges/442d3a7bc65e3e46d8dead728e4d8be8

Then the tests for that service. In our example we can safely assume that our tests capture all the HTTP traffic and thus the following snippet uses an afterEach() hook to verify() that there are no pending or un-matched HTTP requests — as explained before, this means that every expected HTTP requests needs to be matched. When the service being test leaves a request un-matched, it will result in test failure. Whether to use or not to use such an afterEach() hook is in the developer’s choice and should be a discrete decision for every test case.

The expectation for the correct login request uses a matcher function in the following code snippet, allowing to write more complex and advanced assertions whether a request is considered a match. In the example, we specifically check for the Content-Type header and whether it’s a form-encoded entity:

https://gist.github.com/dherges/00865667e26aa0b957e2885335cda267

For asserting the results returned that are returned by the service, we’re flush()‘ing either a 401 Unauthorized or a 200 Ok — each in its own test case. The HTTP response is then handled by our service implementation (which checks for the response.ok flag) and emits a boolean to the subscriber. Obviously, we expect the value to be true for the 200 Ok response and to be false for the 401 Unauthorized case.


Conclusions

Full code examples of the coding patterns and testing approaches presented here can be found on GitHub:

Well, that finishes up our testing snippets for the new HttpClient! The above code snippets serve as a good starting point for writing more sophisticated and advanced test cases for Angular applications that do some sort of HTTP communication with the new HttpClient API. Not only can we use it to assert that services conform to the “contract” of a web API but also can we use it to test components. Imagine a file upload component showing a progress indicator — when we fake out the upload progress in a unit test, it’s possible to assert the visual appearance of that progress indicator. Such an example would be a good follow-up to this story. Another key take-away is that the new HttpClient API greatly reduces the amount of boilerplate code needed for testing. From “developer experience” this is a very very good thing and — fingers crossed — should result in broader acceptance of tests and willingness to write tests!

Related Blog Posts

Here are two good blog posts on the HttpClient and its testing API:


.