Reputation: 2234
Let's say I have this
export class QuestionnaireQuestionsComponent {
questions: Question[] = [];
private loading:boolean = true;
constructor(
private route: ActivatedRoute,
public questionnaireService:QuestionnaireService) {}
ngOnInit(){
this.route.parent.params.subscribe((params:any)=>{
this.questionnaireService.getQuestionsForQuestionnaire(params.id).subscribe((questions)=>{
this.questions = questions;
this.loading = false;
});
});
}
}
My component is actually working pretty well. Problem is that I want to unit test it but I can't figure out how to mock the this.route.parent
object. Here's my test that fails
beforeEach(()=>{
route = new ActivatedRoute();
route.parent.params = Observable.of({id:"testId"});
questionnaireService = jasmine.createSpyObj('QuestionnaireService', ['getQuestionsForQuestionnaire']);
questionnaireService.getQuestionsForQuestionnaire.and.callFake(() => Observable.of(undefined));
component = new QuestionnaireQuestionsComponent(route, questionnaireService);
});
describe("on init", ()=>{
it("must call the service get questions for questionnaire",()=>{
component.ngOnInit();
expect(questionnaireService.getQuestionsForQuestionnaire).toHaveBeenCalled();
});
});
The test fails with this error
TypeError: undefined is not an object (evaluating 'this._routerState.parent')
Upvotes: 58
Views: 62753
Reputation: 1005
For anyone new to this question the angular docs cover this scenario.
As per documentation above:
Create ActivatedRouteStub
class to be used as test double for ActivatedRoute
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { convertToParamMap, ParamMap, Params } from '@angular/router';
/**
* An ActivateRoute test double with a `paramMap` observable.
* Use the `setParamMap()` method to add the next `paramMap` value.
*/
export class ActivatedRouteStub {
// Use a ReplaySubject to share previous values with subscribers
// and pump new values into the `paramMap` observable
private subject = new ReplaySubject<ParamMap>();
constructor(initialParams?: Params) {
this.setParamMap(initialParams);
}
/** The mock paramMap observable */
readonly paramMap = this.subject.asObservable();
/** Set the paramMap observables's next value */
setParamMap(params?: Params) {
this.subject.next(convertToParamMap(params));
};
}
And then use the stub in the test class as follows:
activatedRoute.setParamMap({ id: '1234' });
Upvotes: 21
Reputation: 41
Angular 9 version
Create your mock:
const mockActivatedRoute = {
parent: {
paramMap: of(convertToParamMap(
{ organization: 'fakeOrganization' }
))}
};
Provide it as the Activated Route
providers: [{ provide: ActivatedRoute, useValue: mockActivatedRoute }]
Test it
describe('ngOnInit', () => {
it('should get the route parameter', fakeAsync(() => {
component.ngOnInit();
expect(component.organization).toBe('fakeOrganization');
}));
});'
Code being tested
ngOnInit(): void {
this.route.parent.paramMap.subscribe(params => {
this.organization = params.get('organization');
});
}
Upvotes: 4
Reputation: 4114
Building off of @S.Galarneau 's accepted answer, here is the updated ActivatedRoute interface (angular 4+), with access modifiers, only using a handful of types, as well as using the providers array and integrating with TestBed:
This works great to create a mock interface and use the providers array to use your version of ActivatedRoute:
export class MockActivatedRoute implements ActivatedRoute{
public snapshot;
public url;
public params;
public queryParams: Observable<Params>;
public fragment: Observable<string>;
public data;
public outlet;
public component;
public paramMap;
public queryParamMap;
public routeConfig;
public root;
public parent;
public firstChild;
public children;
public pathFromRoot;
public toString(): string {
return '';
};
}
And use with TestBed and Providers array, only dealing with the fragment property, like this:
beforeEach(async((): void => {
const route = new MockActivatedRoute();
route.fragment = of('#myhash?some_params=some_value');
// because fragment is an Observable, we need `of(value)`
TestBed.configureTestingModule({
// declarations, imports, and providers go next:
providers:[
{ provide: ActivatedRoute, useValue: route},
]
})
Upvotes: 1
Reputation: 191
For scenarios in which you are reading the query params from the ActivatedRouteSnapshot like:
this.someProperty = this.route.snapshot.data.query['paramKey'];
The below code will be helpful in tests for feeding query params data into the route
{
provide: ActivatedRoute,
useValue: {
snapshot: {
data: {
query: {
paramKey: 'paramValue'
}
}
}
}
}
Upvotes: 7
Reputation: 96
To customize a mocked ActivatedRoute's data inside each 'it' block, combine what Kevin Black suggested above
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [YourComponent],
imports: [],
providers: [
{
provide: ActivatedRoute, useValue: {
queryParams: of({ id: 'test' })
}
}
]
})
.compileComponents();
}));
and the below code in the it('') block before instantiating a component using fixture.detectChanges()
it('test', fakeAsync(() => {
let activatedRoute: ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
activatedRoute.queryParams = of({ firstName: 'john', lastName: 'smith' });
fixture.detectChanges(); // trigger ngOninit()
tick();
}));
Upvotes: 6
Reputation: 21688
You can also simple plass {params: {searchTerm: 'this is not me'}} as any) as ActivatedRouteSnapshot
detailds code
(service.resolve(({params: {id: 'this is id'}} as any) as ActivatedRouteSnapshot,
{} as RouterStateSnapshot)as Observable<xyzResolveData>)
.subscribe((data) => {
expect((data as xyzResolveData).results).toEqual(xyzData.results);
});
Upvotes: 3
Reputation: 916
Using TestBed
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [YourComponent],
imports: [],
providers: [
{
provide: ActivatedRoute, useValue: {
params: Observable.of({ id: 'test' })
}
}
]
})
.compileComponents();
}));
Upvotes: 60
Reputation: 2234
AcitvatedRoute is an interface according to angular2 docs, so what I did is implements a MockActivatedRoute
import {Observable} from 'rxjs';
import {Type} from '@angular/core';
import {ActivatedRoute,Route,ActivatedRouteSnapshot,UrlSegment,Params,Data } from '@angular/router';
export class MockActivatedRoute implements ActivatedRoute{
snapshot : ActivatedRouteSnapshot;
url : Observable<UrlSegment[]>;
params : Observable<Params>;
queryParams : Observable<Params>;
fragment : Observable<string>;
data : Observable<Data>;
outlet : string;
component : Type<any>|string;
routeConfig : Route;
root : ActivatedRoute;
parent : ActivatedRoute;
firstChild : ActivatedRoute;
children : ActivatedRoute[];
pathFromRoot : ActivatedRoute[];
toString() : string{
return "";
};
}
and just replace the ActivatedRoute
in my tests for MockActivatedRoute
like this
beforeEach(()=>{
route = new MockActivatedRoute();
route.parent = new MockActivatedRoute();
route.parent.params = Observable.of({id:"testId"});
questionnaireService = jasmine.createSpyObj('QuestionnaireService', ['getQuestionsForQuestionnaire']);
questionnaireService.getQuestionsForQuestionnaire.and.callFake(() => Observable.of(undefined));
component = new QuestionnaireQuestionsComponent(route, questionnaireService);
});
Upvotes: 30