Angular 2 - test for change in route params
Asked Answered
T

2

22

I have a component in angular 2 which responds to changes in the route parameters (the component doesn't reload from scratch because we're not moving out of the main route. Here's the component code:

export class MyComponent{
    ngOnInit() {
        this._routeInfo.params.forEach((params: Params) => {
            if (params['area']){
                this._pageToShow =params['area'];
            }
        });
    }
}

This works a treat and _pageToShow is set appropriate on navigation.

I'm trying to test the behaviour on a change to the route (so a second trigger of the observable but it's refusing to work for me.) Here's my attempt:

it('sets PageToShow to new area if params.area is changed', fakeAsync(() => {
    let routes : Params[] = [{ 'area': "Terry" }];
    TestBed.overrideComponent(MyComponent, {
        set: {
            providers: [{ provide: ActivatedRoute,
                useValue: { 'params': Observable.from(routes)}}]
        }
    });

    let fixture = TestBed.createComponent(MyComponent);
    let comp = fixture.componentInstance;
    let route: ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
    comp.ngOnInit();

    expect(comp.PageToShow).toBe("Terry");
    routes.splice(2,0,{ 'area': "Billy" });

    fixture.detectChanges();
    expect(comp.PageToShow).toBe("Billy");
}));

But this throws a TypeError: Cannot read property 'subscribe' of undefined exception when I run it. If I run it without the fixture.detectChanges(); line it fails as the second expectation fails.

Transfuse answered 26/10, 2016 at 12:54 Comment(0)
K
37

Firstly, you should use a Subject instead of an Observable. The observable only gets subscribed to once. So it will only emit the first set of params. With a Subject, you can keep emitting items, and the single subscription will keep getting them.

let params: Subject<Params>;

beforeEach(() => {
  params = new Subject<Params>();
  TestBed.configureTestingModule({
    providers: [
      { provide: ActivatedRoute, useValue: { params: params }}
    ]
  })
})

Then in your test just emit new values with params.next(newValue).

Secondly, you need to make sure to call tick(). This is how fakeAsync works. You control asynchronous task resolution. Since the observable as asychrounous, the moment we sent the event, it will not get to the subscriber synchronously. So we need to force synchronous behavior with tick()

Here is a complete test (Subject is imported from 'rxjs/Subject')

@Component({
  selector: 'test',
  template: `
  `
})
export class TestComponent implements OnInit {

  _pageToShow: string;

  constructor(private _route: ActivatedRoute) {
  }

  ngOnInit() {
    this._route.params.forEach((params: Params) => {
      if (params['area']) {
        this._pageToShow = params['area'];
      }
    });
  }
}

describe('TestComponent', () => {
  let fixture: ComponentFixture<TestComponent>;
  let component: TestComponent;
  let params: Subject<Params>;

  beforeEach(() => {
    params = new Subject<Params>();
    TestBed.configureTestingModule({
      declarations: [ TestComponent ],
      providers: [
        { provide: ActivatedRoute, useValue: { params: params } }
      ]
    });
    fixture = TestBed.createComponent(TestComponent);
    component = fixture.componentInstance;
  });

  it('should change on route param change', fakeAsync(() => {
    // this calls ngOnInit and we subscribe
    fixture.detectChanges();

    params.next({ 'area': 'Terry' });

    // tick to make sure the async observable resolves
    tick();

    expect(component._pageToShow).toBe('Terry');

    params.next({ 'area': 'Billy' });
    tick();

    expect(component._pageToShow).toBe('Billy');
  }));
});
Karolyn answered 26/10, 2016 at 13:47 Comment(2)
Make sure to scroll down in this solution for the inclusion on the tick() function! I completely missed that the first time. Thanks for the solution, this worked for me :)Hanhhank
I've been able to use this approach for fragment also for internal links: fragment = new Subject<string>();, which is appropriate for hiding/showing tab content, for example.Phinney
B
6

I prefer to get route params and data from ActivatedRouteSnapshot like this this.route.snapshot.params['type']

If you use the same way, you can test it like this

1) In your test providers

{provide: ActivatedRoute, useValue: {snapshot: { params: { type: '' } }}}

2) in your test spec

it('should...', () => {
   component.route.snapshot.params['type'] = 'test';
   fixture.detectChanges();
   // ...
});
Burrussburry answered 22/9, 2018 at 11:11 Comment(2)
Cool, but take care of setting the default value in the first 'it' too, if the params used in more 'it', because the order of the tests is not fix at running. And it works only if the router is not private.Furuncle
should be the accepted answer, no need to create your own subjects like the above answerThermograph

© 2022 - 2024 — McMap. All rights reserved.