How to Unit Test Angular 2 routing params
Asked Answered
M

2

6

Say that I want to simply unit test a component that takes parameters taken from a part of the route. For instance, my component's ngOnInit looks like this:

  ngOnInit() {
    this.route.parent.params.switchMap((params: Params) => this.service.getProject(params['projectId']))
      .subscribe((project: Project) => this.update(project));
  }

How can I now: 1: setup my test so that the component create works at all, so I can unit test other parts

Edited with answer - the example ActivatedRouteStub should be extended with a parent that also has an observable params, and I should have used useClass instead of useValue (forgot to change that back):

@Injectable()
export class ActivatedRouteStub {

    // ActivatedRoute.params is Observable
    private subject = new BehaviorSubject(this.testParams);
    params = this.subject.asObservable();

    // Test parameters
    private _testParams: {};
    get testParams() { return this._testParams; }
    set testParams(params: {}) {
        this._testParams = params;
        this.subject.next(params);
    }

    // ActivatedRoute.snapshot.params
    get snapshot() {
        return { params: this.testParams };
    }
        // ActivatedRoute.parent.params
    get parent() {
        return { params: this.subject.asObservable() };
    }
}

2: provide a value for projectId in my UnitTest?

The examples from the angular2 documentation do not seem to work for switchMap, or even at all (I've used the ActivatedRouteStub from there, but no luck with that so far - params is always undefined).

Edited with answer:

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

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes([{ path: 'projects/:projectId', component: ProjectDetailsComponent }])],
      declarations: [ProjectDetailsComponent],
      schemas: [NO_ERRORS_SCHEMA],
      providers: [{ provide: ProjectsService, useClass: ProjectsServiceStub }, {provide: ActivatedRoute, useClass: ActivatedRouteStub}]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ProjectDetailsComponent);
    component = fixture.componentInstance;
    activatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
    activatedRoute.testParams = { projectId: '123'};
    fixture.detectChanges();
  });

  fit('should create', () => {
    expect(component).toBeTruthy();
  });
});
Monomorphic answered 5/2, 2017 at 12:46 Comment(1)
Does this answer your question? Angular 2 - test for change in route paramsSpalding
L
6

The only way params would be undefined is if you're not creating the stub correctly. Look at the call

this.route.parent.params.switchMap

params is a nested property two levels deep. So as plain JS object you would need

let mock = {
  parent: {
    params: Observable.of(...)
  }
}

If you want to use a classes, you could use something like

class ActivatedRouteStub {

  parent = {
    params: Observable.of({})
  };

  set testParams(params: any) {
    this.parent.params = Observable.of(params);
  }
}

You just use a setter on the testParams to set the value of the parent.params. So then when you do stub.testParams = whatever, the value will be set on the observable of the parent.params.

UPDATE

Aside from the above explanation on how you can implement this, you also have an error in your configuration

{provide: ActivatedRoute, useValue: ActivatedRouteStub}

useValue is supposed to be an object that you create. So you are passing a class, and that class will be what is injected, not an instance of the class. If you want Angular to create it, then you should use useClass. Otherwise you should create the instance yourself, and use that instance as the value

{provide: ActivatedRoute, useValue: new ActivatedRouteStub() }

Notice the instantiation.

Lavella answered 5/2, 2017 at 13:16 Comment(2)
I was just typing pretty much this as an answer to my own question when the doorbell rang, so you get the credits :D I figured it out. After everything I tried (there's half a weekend in getting to this point ...), I forgot to change back useValue to useClass. Then, realising that I should probably be able to debug this test in the browser (yeah I know), I noticed that the parent wasn't an observable. So I changed the ActivatedRouteStub to return the same testParams (will update the test later to allow providing parent params separately)Monomorphic
Updated the question with the answer. And thanks, of course! The new in useValue is also a useful tip.Monomorphic
L
5

If anybody ends up having this problem with Angular 7, the Angular docs give a clear example how to test components dependent on the ActivatedRoute service. See: https://v7.angular.io/guide/testing#activatedroutestub. Just a bit different from the responses above.

Labiate answered 4/3, 2019 at 16:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.