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
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
Beside that, the new API has renamed a few classes: it’s now called a
HttpParams. Notice the type argument for the outgoing/incoming HTTP bodies. When sending a request, you now declare the expected response type (one of
json) and the type of the response body will be either an
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
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
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
HttpClientTestingModule and we’re ready to go:
We still have to use the
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
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:
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 —
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:
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:
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:
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.
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: