Testing Angular 10 Interceptors with Spectator and Jest
Tags: angular, javascript, testing
Published: 2020-09-23 13:42:28

<!-- wp:paragraph -->
<p>There are many reasons to switch from Karma and Jasmine to Jest when Testing Angular:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Jest runs faster than Karma and Jasmine</li><li>Jest supports snapshot testing</li><li>Jest runs tests in parallels</li><li>Jest does not require a browser for testing</li><li>many more...</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>However, what's missing are examples of how to write Angular unit tests in Jest, particularly testing Angular HTTP Interceptors.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Setting up Angular, Spectator, and Jest</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Jest</h3>
<!-- /wp:heading -->

<!-- wp:image {"id":5275} -->
<figure class="wp-block-image"><img src="https://tenmilesquare.com/wp-content/uploads/2020/09/jest.jpeg" alt="" class="wp-image-5275"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>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 <a href="https://github.com/djchi82/angular-jest-spectator-interceptor-test">Github project</a> as a template. <em>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.</em></p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><a href="https://www.amadousall.com/how-to-set-up-angular-unit-testing-with-jest/">How to Set Up Angular Unit Testing with Jest</a></li><li><a href="https://itnext.io/testing-angular-applications-with-jest-and-spectator-c05991579807">Testing Angular applications with Jest and Spectator</a></li><li><a href="https://itnext.io/how-i-do-configure-jest-to-test-my-angular-8-project-2bd84a21d725">How I do configure Jest to test my Angular 8 Project</a></li><li><a href="https://github.com/thymikee/jest-preset-angular">https://github.com/thymikee/jest-preset-angular</a></li><li><a href="https://medium.com/@ole.ersoy/unit-testing-angular-with-jest-b65888ff33f6">Unit Testing Angular with Jest</a></li><li><a href="https://medium.com/angular-in-depth/migrate-your-angular-library-to-jest-faba9579591a">Migrate your Angular library to Jest</a></li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":3} -->
<h3>Spectator</h3>
<!-- /wp:heading -->

<!-- wp:image {"id":5276} -->
<figure class="wp-block-image"><img src="https://tenmilesquare.com/wp-content/uploads/2020/09/1_vwethP80ObAvhrW29WaGEw.png" alt="" class="wp-image-5276"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>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,</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li><a href="https://github.com/ngneat/spectator">https://github.com/ngneat/spectator</a></li><li><a href="https://netbasal.com/spectator-v4-a-powerful-tool-to-simplify-your-angular-tests-bd65a0bf317e">Spectator V4: A Powerful Tool to Simplify Your Angular Tests!</a></li><li><a href="https://engineering.datorama.com/spectator-for-angular-or-how-i-learned-to-stop-worrying-and-love-the-spec-2aa8521c8488">Spectator for Angular or: How I Learned to Stop Worrying and Love the Spec</a></li></ul>
<!-- /wp:list -->

<!-- wp:heading {"level":3} -->
<h3>A Couple of things</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The major thing to keep in mind when using Spectator and jest together is that Specator imports should come from the <code style="background-color: lightgrey;   font-style: italic;">@ngneat/spectator/jest</code> package.</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre class="code">import {createHttpFactory, HttpMethod, SpectatorHttp} from '@ngneat/spectator/jest';</pre>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>Below is the final devDependencies section of <code style="background-color: lightgrey;   font-style: italic;">package.json</code>.</p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre class="code">{<br>
  …<br>
   "devDependencies": {<br>
    "@angular-devkit/build-angular": "~0.901.7",<br>
    "@angular/cli": "~9.1.7",<br>
    "@angular/compiler-cli": "~9.1.9",<br>
    "@ngneat/spectator": "^5.13.3",<br>
    "@types/jest": "^26.0.13",<br>
    "@types/node": "^12.11.1",<br>
    "codelyzer": "^5.1.2",<br>
    "jest": "^26.4.2",<br>
    "jest-preset-angular": "^8.3.1",<br>
    "protractor": "~7.0.0",<br>
    "ts-node": "~8.3.0",<br>
    "tslint": "~6.1.0",<br>
    "typescript": "~3.8.3"<br>
  }<br>
}</pre>
<!-- /wp:html -->

<!-- wp:heading -->
<h2>Angular 10 Interceptor Unit Test</h2>
<!-- /wp:heading -->

<!-- wp:image {"id":5277} -->
<figure class="wp-block-image"><img src="https://tenmilesquare.com/wp-content/uploads/2020/09/1_69L1XNoNowh5KuJMbGMPlg.png" alt="" class="wp-image-5277"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>For this example, we will be testing an Http Interceptor that logs HttpErrorResponses to the console. </p>
<!-- /wp:paragraph -->

<!-- wp:html -->
<pre class="wp-block-preformatted code">
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<any>, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {
    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);
      })
    );
  }
}
</pre>
<!-- /wp:html -->

<!-- wp:paragraph -->
<p>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 <code>next.handle</code>. Then we create a pipe in order to <code style="background-color: lightgrey;   font-style: italic;">tap</code> the response for processing. <em>Note: tap is a <a href="https://rxjs-dev.firebaseapp.com/api/operators/tap">rxjs pipe function</a> that allows us to inspect the data without changing the actual data in the pipe.</em> </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>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. <em>Note in this case we are expecting a custom body in the HttpResponse.</em></p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>The Unit Test</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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. <em>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.</em></p>
<!-- /wp:paragraph -->

<!-- wp:preformatted {"className":"code"} -->
<pre class="wp-block-preformatted code">import {HttpErrorInterceptor} from './http-error.interceptor';<br> import {createHttpFactory, HttpMethod, SpectatorHttp} from '@ngneat/spectator/jest';<br> import {async} from '@angular/core/testing';<br> import {of, throwError} from 'rxjs';<br> import {HttpErrorResponse, HttpRequest, HttpResponse} from '@angular/common/http';<br> describe('HttpErrorInterceptor', () =&gt; {<br> let spectator: SpectatorHttp&lt;HttpErrorInterceptor&gt;;<br>   const createHttp = createHttpFactory({<br>     service: HttpErrorInterceptor<br>   });<br> beforeEach(() =&gt; {<br>     spectator = createHttp();<br>   });<br> test('Http error', async(() =&gt; {<br>     const mockHandler = {<br>       handle: jest.fn(() =&gt; throwError(<br>         new HttpErrorResponse({status: 500, error: {message: 'This is an error'}})))<br>     };<br>     spectator.service.intercept(new HttpRequest&lt;unknown&gt;(HttpMethod.GET, '/thing'), mockHandler)<br>       .subscribe((response) =&gt; {<br>         fail('Expected error');<br>       }, (error =&gt; {<br>         expect(error).toBeTruthy();<br>       }));<br> }));<br> test('Http success', async(() =&gt; {<br>     const mockHandler = {<br>       handle: jest.fn(() =&gt; of(new HttpResponse({status: 500})))<br>     };<br>     spectator.service.intercept(new HttpRequest&lt;unknown&gt;(HttpMethod.GET, '/thing'), mockHandler)<br>       .subscribe((response) =&gt; {<br>         expect(response).toBeTruthy();<br>       }, (error =&gt; {<br>         fail('Expected Successful');<br>       }));<br>   }));<br> });</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>The key here is 1) how the handler is mocked and 2) and how we test the interceptor response.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Mocking the HttpHandler</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The first confusing thing when testing the interceptor is how to mock the HttpHandler. Since Jasmine is removed <code style="background-color: lightgrey;   font-style: italic;">mock</code> and <code style="background-color: lightgrey;   font-style: italic;">SpyOn</code> are off the table. You may notice that <code style="background-color: lightgrey;   font-style: italic;">jest.mock</code> exists, but it doesn't function as expected. This is one of those little Jest quirks I mentioned; <code style="background-color: lightgrey;   font-style: italic;"><a href="https://jestjs.io/docs/en/mock-functions">jest.mock</a></code> 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.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted {"className":"code"} -->
<pre class="wp-block-preformatted code">export declare abstract class HttpHandler {<br>     abstract handle(req: HttpRequest): Observable&lt;HttpEvent&lt;any&gt;&gt;;<br> }</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>This is easily mocked with <code style="background-color: lightgrey;   font-style: italic;">jest.fn()</code></p>
<!-- /wp:paragraph -->

<!-- wp:preformatted {"className":"code"} -->
<pre class="wp-block-preformatted code">const mockHandler = {<br>       handle: jest.fn(() =&gt; throwError(<br>         new HttpErrorResponse({status: 500, error: {message: 'This is an error'}})))<br>     };</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>In the error case, we will instruct the method to throw an HttpErrorResponse and create a custom object for the response body/error.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In the happy path case the mock looks like the following:</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted {"className":"code"} -->
<pre class="wp-block-preformatted code">const mockHandler = {<br>       handle: jest.fn(() =&gt; of(new HttpResponse({status: 200})))<br>     };</pre>
<!-- /wp:preformatted -->

<!-- wp:heading {"level":4} -->
<h4>Testing the Interceptor's Response</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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 <code style="background-color: lightgrey;   font-style: italic;">.subscribe</code> lambda.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted {"className":"code"} -->
<pre class="wp-block-preformatted code">spectator.service.intercept(new HttpRequest&lt;unknownn&gt;(HttpMethod.GET, '/thing'), mockHandler)<br>       .subscribe((response) =&gt; {<br>         expect(response).toBeTruthy();<br>       }, (error =&gt; {<br>         fail('Expected Successful');<br>       }));</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>In this case we are checking that that the interceptor passed the response through as normal, and did not throw an error. </p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Spectator and Unit Testing Fiddly Bits</h4>
<!-- /wp:heading -->

<!-- wp:image {"id":5278} -->
<figure class="wp-block-image"><img src="https://tenmilesquare.com/wp-content/uploads/2020/09/sirterry-1.jpg" alt="" class="wp-image-5278"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Some might note that the code is using spectators <code style="background-color: lightgrey;   font-style: italic;">createHttpFactory</code> instead of  <code style="background-color: lightgrey;   font-style: italic;">createServiceFactory</code>. In this scenario, both will work exactly the same. I'm using <code style="background-color: lightgrey;   font-style: italic;">createHttpFactory</code> in anticipation of adding an HTTP retry.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>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 <code style="background-color: lightgrey;   font-style: italic;">map</code>, you will be able to specify the input using the mocked HttpHandler and test the output in the subscribe portion of the interceptor call. </p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2>Summary</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>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</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><li>Using <code style="background-color: lightgrey;   font-style: italic;">jest.fn()</code> to mock the function of the HttpHandler</li><li>Adding the input variable to the subscribe lambda for testing</li></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>Github source: <a href="https://github.com/djchi82/angular-jest-spectator-interceptor-test">https://github.com/djchi82/angular-jest-spectator-interceptor-test</a></p>
<!-- /wp:paragraph -->