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.

2
  • Do You really need a real HttpClientModule? According to documentation - the HTTP backend needs to be mocked as part of good testing practice. Commented Jul 26, 2017 at 14:36
  • @AleksandrPetrovskij I've read the available documentation. It's quite thin, and HttpClientModule is young enough that there isn't much third-party content available about how to use it. I ran into other issues when trying to use HttpClientTestingModule, but chose not to include them in this post in order to keep it focused on the 'undefined' error. Regardless, Commented Jul 27, 2017 at 6:58

2 Answers 2

3

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
    })
  }
Sign up to request clarification or add additional context in comments.

2 Comments

I'm not using an HttpInterceptor. As I mentioned before, TestService.executeGet works when running in a browser context but fails in a unit test. There's something about the environment difference that seems to be causing the problem.
how do you unit test the interceptor. Do you have a sample to share?
1

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');
  }));
});

3 Comments

Thank you for the example. I appreciate it. That said, it doesn't help me understand the error, nor why it appears to be impossible to unit test without mocks. I understand why mocking is a good idea in tests, but not why the HTTP call fails in the absence of mocks, which is the crux of my question.
I think import of HttpClientModule missing, example
That's it! Thanks again, Aleksandr. If you want to update your answer or post a different one, I'll accept it gladly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.