Stuart Updegrave
Stuart Updegrave

Reputation: 688

Error while unit testing HttpClientModule (Angular 4.3+)

I'm exploring the new Angular HttpClientModule and running into an inexplicable error. The module is new enough that I can't yet find any useful information about how to unit test, and the official documentation doesn't have any examples.

The app contains a service with one method, which passes a URL to http.get. When I call this method in a browser context (aka ng serve), the http call executes normally. When called in the context of a unit test, I get the following error:

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

This is a minimal app generated with Angular CLI. Here's the pertinent code:

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts:

import { Component, OnInit } from '@angular/core';
import { TestService } from './test.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [TestService]
})
export class AppComponent {
  title = 'app';

  constructor(private testSvc: TestService) {}

  ngOnInit() {
    this.testSvc.executeGet('http://www.example.com');
  }
}

test.service.ts:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class TestService {
  constructor(private http:HttpClient) {}

  executeGet(url:string) {
    this.http.get(url)
      .subscribe(
        data => console.log('success', data),
        error => console.log('error', error)
    );
  }
}

test.service.spec.ts:

import { HttpClient, HttpHandler } from '@angular/common/http';
import { TestBed, inject } from '@angular/core/testing';
import { TestService } from './test.service';

describe('TestService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [HttpClient, HttpHandler, TestService]
    });
  });

  it('should executeGet', inject([TestService], (svc: TestService) => {
    expect(svc.executeGet).toBeDefined();
    // this is where the 'undefined' error occurs
    svc.executeGet('http://www.example.com'); 
  }));
});

Any guidance or pointers greatly appreciated.

Upvotes: 3

Views: 1559

Answers (2)

Aleksandr Petrovskij
Aleksandr Petrovskij

Reputation: 1243

Import of HttpClientModule missing

Here is the working example on plnkr

describe('TestService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientModule],
      providers: [TestService]
    });
  });

  it('should executeGet', () => {
    const testService = TestBed.get(TestService);

    expect(testService.executeGet).toBeDefined();
    testService.executeGet('http://www.example.com');
  }));
});

Upvotes: 1

Lekhnath
Lekhnath

Reputation: 4625

Recently I experienced exact same issue where the error message says:

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

For me the source of this error was not using HttpInterceptor properly. If you are also providing custom HttpInterceptor make sure you properly use it. In following code snippet notice how I missed returning Observable, Promise, Array, or Iterable if error status is other than 401. By default undefined is returned by the intercept method instead of Observable, Promise, Array, or Iterable so angular is complaining about that.

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).catch(err => {
      if(err instanceof HttpErrorResponse) {
        if(err.status === 401) {
          this.store.dispatch(this.authAction.unauthorized(err));
          return Observable.throw(err);
        }
      }
    })
  }

and the fix to is was following code snippet.

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).catch(err => {
      if(err instanceof HttpErrorResponse) {
        if(err.status === 401) {
          this.store.dispatch(this.authAction.unauthorized(err));
        }
      }
      return Observable.throw(err); //Actually this line of code
    })
  }

Upvotes: 3

Related Questions