There are many reasons to switch from Karma and Jasmine to Jest when Testing Angular:
Jest runs faster than Karma and Jasmine
Jest supports snapshot testing
Jest runs tests in parallels
Jest does not require a browser for testing
many more…
However, what’s missing are examples of how to write Angular unit tests in Jest, particularly testing Angular HTTP Interceptors.
Setting up Angular, Spectator, and Jest
For the purpose of this article, we will assume that you have an Angular project already set up with Spectator and Jest. If not, I will provide you with some links on how to setup Angular with these libraries.
Jest
While the focus of this post is NOT on how to convert Angular from Karma and Jasmine to Jest, below is a list of resources on how to do this conversion yourself. You can also use my Github project as a template. I should mention that Jest can be a bit quirky if you are used to using other testing frameworks, but these quirks are worth it.
Spectator is an amazing library that reduces the wordy boilerplate code for setting up Angular Unit Tests to only a few lines. It has a few quirks that are absolutely worth it for the value it provides,
For this example, we will be testing an Http Interceptor that logs HttpErrorResponses to the console.
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor, HttpErrorResponse, HttpResponse
} from '@angular/common/http';
import { Observable, throwError} from 'rxjs';
import {catchError, tap} from 'rxjs/operators';
/**
* Intercepts HttpRequests and logs any http responses of 3xx+
* In the future we can make this a conditional retry based on the status code.
*
*/
@Injectable({ providedIn: 'root' })
export class HttpErrorInterceptor implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(tap(() => {}),
catchError((error) => {
if (error instanceof HttpErrorResponse) {
if (error.error && error.error.message) {
console.log('status: ' + error.status + '\nmessage: ' + error.error.message);
} else {
console.log(error);
}
}
return throwError(error);
})
);
}
}
What this code does is intercept an HttpRequest from the application and logs the response to the console when an HttpErrorResponse is returned. The HttpHandler is used to execute the request next.handle. Then we create a pipe in order to tap the response for processing. Note: tap is a rxjs pipe function that allows us to inspect the data without changing the actual data in the pipe.
In this case, we catch the HttpErrorResponse, allowing any non-error HttpResponse to pass through. Once the Response is caught we can inspect the error message and log it to console. Note in this case we are expecting a custom body in the HttpResponse.
The Unit Test
In this unit test, we will be checking that a response with a 2xx will pass through and that an Error Response will be thrown. For more advanced testing the console could be mocked and we can check that the console.log has been called. This is out of scope for this article.
The key here is 1) how the handler is mocked and 2) and how we test the interceptor response.
Mocking the HttpHandler
The first confusing thing when testing the interceptor is how to mock the HttpHandler. Since Jasmine is removed mock and SpyOn are off the table. You may notice that jest.mock exists, but it doesn’t function as expected. This is one of those little Jest quirks I mentioned; jest.mock is used to mock a package and not an object. In this case, we will build an object that looks like HttpHandler interface and mock the methods expected. Below is the HttpHandler interface. As you can see it only has one method.
Now that we have the HttpHandler mocked, how do we actually test that the interceptor does anything? The key here is to specify an input on the .subscribe lambda.
In this case we are checking that that the interceptor passed the response through as normal, and did not throw an error.
Spectator and Unit Testing Fiddly Bits
Some might note that the code is using spectators createHttpFactory instead of createServiceFactory. In this scenario, both will work exactly the same. I’m using createHttpFactory in anticipation of adding an HTTP retry.
It is also important to note that this interceptor doesn’t actually modify the Response and the tests are a bit weak. This is meant to be a basic framework to get you started with testing interceptors. If you have an interceptor that modifies the HttpRespond using map, you will be able to specify the input using the mocked HttpHandler and test the output in the subscribe portion of the interceptor call.
Summary
Using Spectator and Jest with Angular 10 is a very powerful combination. The trick is to either have a full understanding of Jest and Spectator, or have a ready source of examples to draw from. I hope this article can provide you a rough understanding of how to use Jest in concert with Spectator to test Angular HttpInterceptors. The keys here are
Using jest.fn() to mock the function of the HttpHandler
Adding the input variable to the subscribe lambda for testing
(function(l) {
if (!l){window.lintrk = function(a,b){window.lintrk.q.push([a,b])};
window.lintrk.q=[]}
var s = document.getElementsByTagName("script")[0];
var b = document.createElement("script");
b.type = "text/javascript";b.async = true;
b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js";
s.parentNode.insertBefore(b, s);})(window.lintrk);
var astra = {"break_point":"768","isRtl":"","is_scroll_to_id":"","is_scroll_to_top":"1","is_header_footer_builder_active":"1","responsive_cart_click":"flyout","is_dark_palette":"","revealEffectEnable":"","edit_post_url":"https://tenmilesquare.com/wp-admin/post.php?post={{id}}&action=edit","ajax_url":"https://tenmilesquare.com/wp-admin/admin-ajax.php","infinite_count":"2","infinite_total":"0","pagination":"number","infinite_scroll_event":"scroll","no_more_post_message":"No more posts to show.","grid_layout":"1","site_url":"https://tenmilesquare.com","blogArchiveTitleLayout":"","blogArchiveTitleOn":"","show_comments":"Show Comments","masonryEnabled":"","blogMasonryBreakPoint":"0"};
//# sourceURL=astra-theme-js-js-extra