angularjasmineangular-httpclient

Trying to run Angular HttpClient Jasmine test against live REST API: nothing happens


I have a simple "Contacts" REST service written in .Net Core. It works fine.

I'm trying to write an Angular 8.3 client to talk to it.

The first two things I did were:

  1. Create Contact.ts and Note.ts (corresponding to the REST models)
  2. Create an Angular service to communicate with the .Net Core REST API.

I thought maybe the best way to test the service would be to use the auto-generated unit test, contacts.service.spec.ts. I do NOT want to use a mock service: I want to use a "live" HttpClient so my Angular "contacts" service can talk directly to the .Net Core API.

It's not working: no errors, no warnings: the test just "passes" without even trying to send an HTTP message or wait for the HTTP response.

How can I debug my Angular service against the live REST service?

Can I use the contacts.service.spec.ts Jasmine test/Karma runner, or should I do "something else" to step through code execution?

models/contact.ts:

export class Contact {
  ContactId?: number;
  Name: string;
  EMail: string;
  Phone1: string;
  Phone2: string;
  Address1: string;
  Address2: string;
  City: string;
  State: string;
  Zip: string; 
}

models/note.ts

export class Note {
  NoteId?: number;
  Text: string;
  Date: Date;
  ContactId?: number;
}

services/contacts.service.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';

import { Contact } from '../models/Contact';
import { Note } from '../models/Note';


@Injectable({
  providedIn: 'root'
})
export class ContactsService {

  myAppUrl: string;
  myApiUrl: string;
  httpOptions = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json; charset=utf-8'
    })
  };

  constructor(private http: HttpClient) {
    this.myAppUrl = 'http://localhost:53561/';  // environment.appUrl;
    this.myApiUrl = 'api/Contacts/';
  }

  getContacts(): Observable<Contact[]> {
    const url = this.myAppUrl + this.myApiUrl;
    return this.http.get<Contact[]>(url)
    .pipe(
      retry(1)
    );
  }
}

services/contacts.service.spec.ts

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ContactsService } from './contacts.service';

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

  it('should be created', () => {
    const service: ContactsService = TestBed.get(ContactsService);
    expect(service).toBeTruthy();
  });

  it('should retrieve all contacts', () => {
    const contactsService: ContactsService = TestBed.get(ContactsService);
    let observable = contactsService.getContacts();
    expect(observable).toBeTruthy();
    debugger;
    observable.subscribe(data => {
      debugger;
      console.log("Done");
    },
    error => {
      debugger;
      console.error("observable error");
    });
  });
});

ng test

ng test


I've tried adding done() to my service test, and tried instantiating HttpClient inside the unit test. It's still not making any HTTP calls to the REST server :(

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { HttpClient } from '@angular/common/http';
import { ContactsService } from './contacts.service';

describe('ContactsService', () => {
  let httpClient: HttpClient;
  let service: ContactsService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [ContactsService, HttpClient]
    });
    // ERROR:
    //   "Timeout - Async callback was not invoked within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
    // Tried changing to 20000 - still getting Timeout...
    let originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000; // Getting timeout @default 5000
    httpClient = TestBed.get(HttpClient);
    //service = TestBed.get(ContactsService);
    service = new ContactsService(httpClient);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should retrieve all contacts', (done: DoneFn) => {
      service.getContacts().subscribe(data => {
        done();
    });
  });
});

Current error (despite manually updating timeout value inside of test)

Error: Timeout - Async callback was not invoked within 20000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
Error: Timeout - Async callback was not invoked within 20000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
    at <Jasmine>

Thanks to both Pytth and wessam yaacob. Here's how I got it working:

  1. Configure CORS on the .Net Core REST service

     public class Startup
       ...
       public void ConfigureServices(IServiceCollection services)
         ...
         services.AddCors(options => {
           options.AddPolicy("CorsPolicy",
             builder => builder.AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());
         });
       ...
       public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
         ...
         app.UseCors("CorsPolicy");
    

I was already doing thing - but it's useful to note here

  1. Use HttpClientModule and HttpClient instead of HttpClientTestingModule and HttpTestingController

    I never WANTED to use "HttpClientTestingModule", because I wanted to talk to the live service - not make a mocked call.

    I changed my code substantially along the way, but here's the unit test I finally wound up with:

     import { TestBed } from '@angular/core/testing';
     import { HttpClientModule, HttpClient, HttpErrorResponse } from '@angular/common/http';
    
     import { BlogPostService } from './blog-post.service';
    
     describe('BlogPostService', () => {
       let httpClient: HttpClient;
       let service: BlogPostService;
    
       beforeEach(() => {
         TestBed.configureTestingModule({
           imports: [HttpClientModule],
         });
         httpClient = TestBed.get(HttpClient);
         service = TestBed.get(BlogPostService);
       });
    
       it('should be created', () => {
         expect(service).toBeTruthy();
       });
    
       it('should retrieve blog posts', (done: DoneFn) => {
         service.getBlogPosts().subscribe(data => {
           done();
         });
       });
     });
    
  2. Final Note:

CORS does NOT seem to work UNLESS I used HTTPS:

    export class BlogPostService {
      ...
      constructor(private http: HttpClient) {
          this.myAppUrl = 'https://localhost:44330/'  // CORS error if 'http://localhost:44330/'
          this.myApiUrl = 'api/BlogPosts/';
          ...

PS: I know all about the "academic distinction" between "unit tests" and "integration tests". I was merely hoping the Angular "unit test" framework would give me the same convenience I have using static void main (String[] args) code in the Java world.

It turns out that's very definitely NOT the case...

I haven't tried an e2e test for this scenario yet - it was easier to just create a dummy page (a simple component) and use that for testing...


Solution

  • If you are going to test your service against your real rest api so you have to replace HttpClientTestingModule with HttpClientModule

    HttpClientTestingModule is just only used for mocking

    aslo in your setup file , you have to add the test domain url in the accepted origin to avoid ->> CORS policy: No 'Access-Control-Allow-Origin'

    in your case http://localhost:9876

       public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            { 
                  ......
                  app.UseCors(options =>
                  options.WithOrigins("http://localhost:9876") 
                  ......
            }