0
votes

I have a service where where my restpoints are being defined in the constructor so I've set up as variables. When I run my unit tests, these properties are returning as undefined. I'm relatively new to unit testing so I'm not sure if I missed an obvious step...

Here's my service.ts:

import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class PulldownService {

  public pulldownData: any = [];
  public restURL;

  constructor(public http: HttpClient, public router: Router) {
    this.getEnvConf().subscribe(
        res => {
          this.restURL = res[window.location.hostname];
        }
    );
  }

  getEnvConf(): Observable<any> {
    return this.http.get('./assets/data/environment-config.json');
  }
  postClaim() {
    let headerTarget;
    if (this.restURL['target_env']) {
      headerTarget = {'Accept': 'application/json', 'Content-Type': 'application/json', 'target_env': this.restURL['targetEnv']};
    } else {
      headerTarget = {'Accept': 'application/json', 'Content-Type': 'application/json'};
    }
    const httpHeaders = new HttpHeaders(headerTarget);
    const options = { headers: httpHeaders };
    return this.http.post( this.restURL['restEndPoint'],
      { 'names' : [
        'PersonRelationType',
        'State',
        ...
        ...

      ]}, options ).subscribe(
      data => {
        // Success!
        this.pulldownData.push(data);
      }
      ,
      (err: HttpErrorResponse) => {
        if (err.error instanceof Error) {
          // A client-side or network error occurred.
          this.router.navigateByUrl('/error');
          console.log('An error occurred:', err.error.message);
        } else {
          // The backend returned an unsuccessful response code.
          this.router.navigateByUrl('/error');
          console.log(`Backend returned code ${err.status}, body was: ${err.error}`);
        }
      },
      () => {
        this.router.navigateByUrl('/getstart');
      }
    );
  }
}

Here's my service.spec.ts

import { PulldownService } from './pulldown.service';
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpRequest } from '@angular/common/http';

describe('PulldownService', () => {
  let service: PulldownService;
  let httpTestingController: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule, RouterTestingModule ],
      providers: [ PulldownService ]
    });
    httpTestingController = TestBed.get(HttpTestingController);
    service = TestBed.get(PulldownService);
  });
  describe('getEnvConfig', () => {
    it('should call get with the correct url', () => {
      // no subscribe method called in getEnvConfig
      // service.getEnvConf().subscribe();
      const req = httpTestingController.expectOne('./assets/data/environment-config.json');
      req.flush({ 'restEndPoint': 'http://localhost:8080/typelist?version=1', 'target_env': null});
      httpTestingController.verify();
      // expect(req.request.url.endsWith('environment-config.json')).toEqual(true);
    });
  });
  describe('postClaim', () => {

    it('should be called with proper arguments', () => {
      const responseForm = '<form />';
      const pulldownService = TestBed.get(PulldownService);
      const http = TestBed.get(HttpTestingController);
      let postResponse;

      pulldownService.postClaim().subscribe((response) => {
        postResponse = response;
      });
      http.expectOne((request: HttpRequest<any>) => {
        return request.method === 'POST'
          && request.url === 'http://localhost:8080/typelist?version=1'
          && JSON.stringify(request.body) === JSON.stringify({
            names: ['State']
          })
          && request.headers.get('Accept') === 'application/json'
          && request.headers.get('Content-Type') === 'application/json'
          && request.headers.get('target_env') === null;
      }).flush(responseForm);
      expect(postResponse).toEqual(responseForm);
    });
  });
})

The error I keep getting is : TypeError: Cannot read property 'target_env' of undefined. Upon removing 'target_env' from my function, I see a new, different error for 'restEndPoint'.

Any help on this would be greatly appreciated. Thanks

2

2 Answers

0
votes

The errors for target_env and restEndPoint are because this.restURL is undefined. In your first unit test, you've mocked the call properly, but you should return { 'localhost' : { 'restEndPoint': 'http://localhost:8080/typelist?version=1', 'target_env': null } }, because in the constructor of PulldownService you're setting this.restURL = res[window.location.hostname] (as opposed to just this.restURL = res). In your second unit test, you didn't mock the initial get at all.

Since all of your unit tests will probably need this handling, I'd suggest moving that mocked call up into the beforeEach(), after service = TestBed.get(PulldownService);

Side note: you don't need to define pulldownService and http again in your second test since you already have httpTestingController and service.

0
votes

Your postClaim() method needs restURL to be defined. restURL is only defined if the service has received the response to the request it sent in the constructor.

But in your second unit test, you never tell the http testing controller to flush any response for this first request before calling postClaim(). So restURL is undefined, hence the error.

Even if you fix your test, your service has a race condition: if called to early, the postClaim() method will have the same error as in your test. Why don't you use the environments support of the CLI to define static, known in advance, configuration options?