title: Angular 2 Unit Testing Observable Errors (HTTP)
body: <p>I am trying to write unit tests for my API service but have some trouble catching HTTP errors. I am following this <a href="https://semaphoreci.com/community/tutorials/testing-services-in-angular-2" rel="nofollow noreferrer">guide</a> along with the Angular2 docs since the guide is (slightly) out of date in some minor areas.</p>
  
  <p>All unit tests pass apart from those where an error is thrown by the service (due to error HTTP status code). I can tell this by logging out <code>response.ok</code>. From what i've read this has something to do with the unit tests not executing asynchronously, hence, not waiting for the error response. However, I have no idea why this is the case here since I have used the <code>async()</code> utility function in the <code>beforeEach</code> method.</p>
  
  <h1>API Service</h1>
  
  <pre><code>get(endpoint: string, authenticated: boolean = false): Observable&lt;any&gt; {
      endpoint = this.formatEndpoint(endpoint);
      return this.getHttp(authenticated) // Returns @angular/http or a wrapper for handling auth headers
          .get(endpoint)
          .map(res =&gt; this.extractData(res))
          .catch(err =&gt; this.handleError(err)); // Not in guide but should work as per docs
  }
  private extractData(res: Response): any {
      let body: any = res.json();
      return body || { };
  }
  
  private handleError(error: Response | any): Observable&lt;any&gt; {
      // TODO: Use a remote logging infrastructure
      // TODO: User error notifications
      let errMsg: string;
      if (error instanceof Response) {
          const body: any = error.json() || '';
          const err: string = body.error || JSON.stringify(body);
          errMsg = `${error.status} - ${error.statusText || ''}${err}`;
      } else {
          errMsg = error.message ? error.message : error.toString();
      }
      console.error(errMsg);
      return Observable.throw(errMsg);
  }
  </code></pre>
  
  <h1>Error unit test</h1>
  
  <pre><code>// Imports
  
  describe('Service: APIService', () =&gt; {
      let backend: MockBackend;
      let service: APIService;
  
      beforeEach(async(() =&gt; {
          TestBed.configureTestingModule({
              providers: [
                  BaseRequestOptions,
                  MockBackend,
                  APIService,
                  {
                      deps: [
                          MockBackend,
                          BaseRequestOptions
                      ],
                      provide: Http,
                          useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) =&gt; {
                              return new Http(backend, defaultOptions);
                          }
                  },
                  {provide: AuthHttp,
                      useFactory: (http: Http, options: BaseRequestOptions) =&gt; {
                          return new AuthHttp(new AuthConfig({}), http, options);
                      },
                      deps: [Http, BaseRequestOptions]
                  }
              ]
          });
          const testbed: any = getTestBed();
          backend = testbed.get(MockBackend);
          service = testbed.get(APIService);
      }));
  
      /**
       * Utility function to setup the mock connection with the required options
       * @param backend
       * @param options
       */
      function setupConnections(backend: MockBackend, options: any): any {
          backend.connections.subscribe((connection: MockConnection) =&gt; {
              const responseOptions: any = new ResponseOptions(options);
              const response: any = new Response(responseOptions);
              console.log(response.ok); // Will return false during the error unit test and true in others (if spyOn log is commented).
              connection.mockRespond(response);
          });
      }
  
      it('should log an error to the console on error', () =&gt; {
          setupConnections(backend, {
              body: { error: `Some strange error` },
              status: 400
          });
          spyOn(console, 'error');
          spyOn(console, 'log');
  
          service.get('/bad').subscribe(null, e =&gt; {
              // None of this code block is executed.
              expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
              console.log("Make sure an error has been thrown");
          });
  
          expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown."); // Fails
      });
  </code></pre>
  
  <h1>Update 1</h1>
  
  <p>when I check the first callback, response.ok is undefined. This leads me to believe that there is something wrong in the <code>setupConnections</code> utility.</p>
  
  <pre><code>    it('should log an error to the console on error', async(() =&gt; {
          setupConnections(backend, {
              body: { error: `Some strange error` },
              status: 400
          });
          spyOn(console, 'error');
          //spyOn(console, 'log');
  
          service.get('/bad').subscribe(res =&gt; {
              console.log(res); // Object{error: 'Some strange error'}
              console.log(res.ok); // undefined
          }, e =&gt; {
              expect(console.error).toHaveBeenCalledWith("400 - Some strange error");
              console.log("Make sure an error has been thrown");
          });
  
          expect(console.log).toHaveBeenCalledWith("Make sure an error has been thrown.");
      }));
  </code></pre>
  
  <h1>Update 2</h1>
  
  <p>If, instead of catching errors in the get method I do it explicitly in map then still have same problem. </p>
  
  <pre><code>get(endpoint: string, authenticated: boolean = false): Observable&lt;any&gt; {
      endpoint = this.formatEndpoint(endpoint);
      return this.getHttp(authenticated).get(endpoint)
          .map(res =&gt; {
              if (res.ok) return this.extractData(res);
              return this.handleError(res);
          })
          .catch(this.handleError);
  }
  </code></pre>
  
  <h1>Update 3</h1>
  
  <p>After some discussion <a href="https://github.com/angular/angular/issues/13690" rel="nofollow noreferrer">this issue</a> submitted</p>