1

I'm finding that testing an observable is causing this Karma 'it' test to be run twice. The first fails and the second passes.

I'm using a service that gets and sets the router's query parameters. It is storing the state of the app on the URL. I want to test that the state is getting stored properly. The test uses RouterTestingModule. Unless I am mistaken (likely) I shouldn't have to create my own mock router object.

I have found this post especially helpful for using done() with a service inject.

Below is my test setup.

import {inject, TestBed  } from '@angular/core/testing';

import { ArQueryService } from './ar-query.service';
import { Router, ActivatedRoute} from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing';

fdescribe('ArQueryService', () => {
  let service: ArQueryService;
  let router: Router;
  let route: ActivatedRoute;
  let queryParamsDefault: Object;
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [ ArQueryService ],
      imports: [ RouterTestingModule ]
    })
    service = TestBed.inject(ArQueryService);

    router = TestBed.get(Router)
    route = TestBed.get(ActivatedRoute)

    queryParamsDefault = {
      includeRealtime: true,
      onlyBGC: false,
      onlyDeep: false,
      arHourRange: [-18, 18],
      arDate: "2010-01-01T00",
      displayGlobally: true
    }
  });

The test below injects the service and inputs a done. The test, located inside Observable checks that the query parameter keys are the same as those set in the TestBed configuration. I'm supposed to include the done() after all the tests have been described, otherwise, the tests will be skipped.

  it('should set url with state', done => {
    const queryParamsDefaultKeys = Object.keys(queryParamsDefault)
    inject([ArQueryService], (service: ArQueryService) => {
      service.setURL()
      route.queryParamMap.subscribe( params => {
        const equal = params.keys === queryParamsDefaultKeys 
        console.log('equal:', equal)
        console.log(queryParamsDefaultKeys, params.keys)
        if (params.keys.length == 0 ) {
          console.log('no parameters set. not testing')
        }
        else {
          console.log('here be parameter keys')
          expect(params.keys).toEqual(queryParamsDefaultKeys)
        }
        done();
      })
    })();

});

What is strange is that this is getting run twice. The first round fails since the router does not have query parameters. The second does and passes.

What is probably happening is the first round occurs from calling setURL(). After the params are set, the second round is run and passes.

My dirty hack is to check if there are any query params before testing. Does anyone know of a better way? I'd like to run this test once after the query parameters have been set.

Thank you in advance!

1 Answers1

1

The router returns a promise when doing router.navigate or router.navigateByUrl and awaiting these to be successful before doing your assertions can help you. You can also potentially wait for router.initialNavigation().

But I think something like this can help you.

import { filter } from 'rxjs/operators';
.....
it('should set url with state', done => {
    const queryParamsDefaultKeys = Object.keys(queryParamsDefault)
    // you already have a handle on ArQueryService, not sure why you're injecting it again
    inject([ArQueryService], (service: ArQueryService) => {
      service.setURL()
      route.queryParamMap.pipe(
        filter(params => !!params.keys.length), // filter out any emissions where keys is an empty array.
      ).subscribe( params => {
        expect(params.keys).toEqual(queryParamsDefaultKeys);
        done();
      })
    })();

});
AliF50
  • 16,947
  • 1
  • 21
  • 37
  • 1
    That did it. Ideally, it would be good to wait for the initial navigation as you have suggested, but using the filter function is a good pragmatic solution. Thank you for this answer, and for spotting the unnecessary injection. – tylertucker202 May 02 '20 at 06:42