4

We have a generic service to make and receive calls to backend controllers. In Angular 4 all the tests were working, but due to new libraries and changes they no longer work. I am now trying to re-write these using HttpClientTestingModule and HttpTestingController.

So our concrete class has a number of functions and for the moment I'm concentrating on our getRequest method:

constructor(
    private configService: ConfigService,
    private userService: UserService,
    private httpClient: HttpClient,
    private errorMapperService: ErrorMapperService) {
  }

  public getRequest(uri: string): Observable<any> {

    const fullUri = this.buildServiceUri(uri);
    const requestHeaders = this.getDefaultRequestHeadersObservable();

    return forkJoin(fullUri, requestHeaders)
      .pipe(flatMap((results: [string, HttpHeaders]) => this.httpClient.get(results[0], { withCredentials: true, headers: results[1] })),
        catchError(this.errorMapperService.handleError));

  }

At the moment in my spec file I have:

fdescribe('Api Service', () => {

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientModule,
        HttpClientTestingModule
      ],
      providers: [
        { provide: ApiService },
        { provide: ConfigService },
        { provide: ErrorMapperService },
        { provide: UserService }
      ]
    });
  });

  describe('getRequest', () => {

    const uiServiceUri: string = 'uiServiceUri';
    const expectedResponse: any = { data: '1234', myDate: new Date(2017, 0, 1, 0, 0, 0) };
    const user = new User();
    user.userSession = '<SESSIONTOKEN>';

    let configService: ConfigService;
    let userService: UserService;
    let apiService: ApiService;
    let errorMapperService: ErrorMapperService;

    let httpTestingController: HttpTestingController;

    beforeEach(() => {
      configService = TestBed.get(ConfigService);
      userService = TestBed.get(UserService);
      apiService = TestBed.get(ApiService);
      errorMapperService = TestBed.get(ErrorMapperService);

      httpTestingController = TestBed.get(HttpTestingController);

    });

    describe('with response object',
      () => {

        it('Gets expected response and converts dates to date objects', async(() => {
          apiService.getRequest('test')
            .subscribe((responseObject) => {
              expect(responseObject).toEqual(expectedResponse);
            });

          httpTestingController.expectOne('test').flush(expectedResponse);

          httpTestingController.verify();
        }));
     });
    });

The issue is that if I initially leave out the last 2 lines of code against the httpTestingController, then my expect never gets reached and the test harness says 'no expectation found'. If I introduce those 2 lines back in, then I receive a fail that states:

Failed: Expected one matching request for criteria "Match URL: test", found none. Requests received are: GET http://localhost:9876/api/User/, GET http://localhost:9876/api/Config/UiServiceUri, GET http://localhost:9876/api/User/.

So then I change the URL to the main one such that the test controller line becomes:

httpTestingController.expectOne('http://localhost:9876/api/User/');

then I receive a similar error stating that only one was expected, but found 2. I'm guessing this is because of my constructor of the concrete class. How should I be writing this test?

jonrsharpe
  • 99,167
  • 19
  • 183
  • 334
bilpor
  • 2,357
  • 5
  • 21
  • 55
  • `expect(responseObject).toEqual(expectedResponse);` will never be reached because you don't flush the response. It's unclear why you expected a request to `test` or, given that the output in that case shows two requests to `.../User`, why you then tried to `expectOne`. Please give a [mre]. Also note there's guidance on testing using the HttpClientTestingModule in https://angular.io/guide/http#testing-http-requests. – jonrsharpe May 13 '20 at 14:25
  • @jonrsharpe Yes, Sorry straight after the `expectOne` line I have a flush, but it doesn't get reached because of the `expectOne` condition. I just put that, because I didn't know what else to put to try and make it reach my expect condition. The testcontroller only has about 4 methods on it and none of them seem to be what I really need. – bilpor May 13 '20 at 14:42
  • Again, please provide a [mre]. There are various services and methods we cannot see, so it's impossible to reproduce locally. – jonrsharpe May 13 '20 at 14:58
  • 1
    there's too much code for me to create a minimal reproducible example. But I think my main issue is what should I be using from the httpTestingController object to get to my expect line. There is only `expectOne`, `expectNone`, `match` and `verify`. From the link in @jonrsharpe comment, it seems to me that in my beforeEach, they used `inject` where I used `get` but changing to `inject` still made no difference. – bilpor May 13 '20 at 15:07
  • Then it also seems fair to say that it's too much code to expect us to trawl through in search of the problem for you. Creating a minimal example is the first step of debugging. [This MRE](https://gist.github.com/textbook/fd7c8a66a8f650634c8e65be4debbf25) works fine for me, for example. – jonrsharpe May 13 '20 at 15:09
  • Hi @jonrsharpe thanks for trying to show me an MRE. I tried to follow this, but because my constructor expects, 3 more services, it errors with a NullInjector error against those services, if I introduce them back, I'm back to my original issue – bilpor May 14 '20 at 07:01
  • It's not clear to me why you might expect otherwise. I didn't say that was a drop-in test for your service, it's just to show what a [mre] actually looks like. You need to isolate the error to something other people can reproduce. Read https://ericlippert.com/2014/03/05/how-to-debug-small-programs/, and https://angular.io/guide/testing. – jonrsharpe May 14 '20 at 07:11

0 Answers0