Search code examples
unit-testingangularangular2-testing

Angular 2/Jasmine, updating an activated route params subscription within each describe/it block


Given a simple component that subscribes to the activated route query params in ngOnInit:

export class FooComponent implements OnInit {
  private queryParams: any;

  constructor(
    private activatedRoute: ActivatedRoute
  ) { }

  ngOnInit() {
    this.activatedRoute.queryParams.subscribe(params => this.queryParams = params);
  }

  active(foo: number): boolean {
    return this.queryParams['foo'] &&
      foo === +this.queryParams['foo'];
  }
}

The active function should return true when the foo query param is present and its value matches the supplied parameter.

In the accompanying unit tests for this component, I want to change the value of the query params within each it block to test the query param not being present, matching the parameter and not matching the parameter.

describe('FooComponent', () => {
  let component: FooComponent;
  let fixture: ComponentFixture<FooComponent>;
  let activatedRoute: ActivatedRoute;

  class MockActivatedRoute {
    queryParams = Observable.of({});
  }

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [FooComponent],
      providers: [
        { provide: ActivatedRoute, useClass: MockActivatedRoute }
      ]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(FooComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
  });

  describe('active', () => {
    it('should return false if the foo query param is not present', () => {
      activatedRoute.queryParams = Observable.of({});
      let result = component.active(100);
      expect(result).toBe(false);
    });

    it('should return false if the foo query param does not match the supplied parameter', () => {
      activatedRoute.queryParams = Observable.of({ foo: '500' });
      let result = component.active(100);
      expect(result).toBe(false);
    });

    it('should return true if the foo query param does not match the supplied parameter', () => {
      activatedRoute.queryParams = Observable.of({ foo: '500' });
      let result = component.active(500);
      expect(result).toBe(true);
    });
  });
});

Rather the value of the private queryParams member of the FooComponent class does not update within each it block. I've tried the various methods of async, fixture.whenStable(), and fakeAsync/tick.

How do I update the value of the subscription for each unit test?


Solution

  • It's because you are assigning a new Observable, but the client is already subscribed to the first Observable. This happens because ngOnInit is called when you first call fixture.detectChanges(). If you waited to called fixture.detectChanges() after you assign the new Observable to the queryParams, then that Observable would be used.

    Another option (maybe preferred) is to instead of using an Observable, you can use a Subject. With this, you can control when data is emitted, and what to emit.

    import { Subject } from 'rxjs/Subject'
    import { fakeAsync, tick } from
    
    class MockActivatedRoute {
      queryParams = new Subject<any>();
    }
    
    let route: MockActivatedRoute;
    
    beforeEach(() => {
      /* configure */ 
    
      route = <MockActivatedRoute>TestBed.get(ActivatedRoute);
    })
    
    it('', fakeAsync(() => {
      route.queryParams.next(newparams);    // emit something
      tick();                               // wait for resolution
      fixture.detectChanges();              // detect changes (for ui)
      expect(...)
    }))
    

    I say this options might be preferred as it allows for emitting multiple values in the same test.