How to unit test a component that depends on parameters from ActivatedRoute?
Solution 1
The simplest way to do this is to just use the useValue
attribute and provide an Observable of the value you want to mock.
RxJS < 6
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
...
{
provide: ActivatedRoute,
useValue: {
params: Observable.of({id: 123})
}
}
RxJS >= 6
import { of } from 'rxjs';
...
{
provide: ActivatedRoute,
useValue: {
params: of({id: 123})
}
}
Solution 2
In angular 8+ there is the RouterTestingModule
, which you can use in order to have access to the ActivatedRoute
or Router
of the component. Also you can pass routes to the RouterTestingModule
and create spies for the requested methods of route.
For example in my component I have:
ngOnInit() {
if (this.route.snapshot.paramMap.get('id')) this.editMode()
this.titleService.setTitle(`${this.pageTitle} | ${TAB_SUFFIX}`)
}
And in my test I have:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ProductLinePageComponent ],
schemas: [NO_ERRORS_SCHEMA],
imports: [
RouterTestingModule.withRoutes([])
],
})
.compileComponents()
}))
beforeEach(() => {
router = TestBed.get(Router)
route = TestBed.get(ActivatedRoute)
})
and later in the 'it' section:
it('should update', () => {
const spyRoute = spyOn(route.snapshot.paramMap, 'get')
spyRoute.and.returnValue('21')
fixture = TestBed.createComponent(ProductLinePageComponent)
component = fixture.componentInstance
fixture.detectChanges()
expect(component).toBeTruthy()
expect(component.pageTitle).toBe('Edit Product Line')
expect(component.formTitle).toBe('Edit Product Line')
// here you can test the functionality which is triggered by the snapshot
})
In a similar way, I think you can test directly the paramMap
via the spyOnProperty
method of jasmine, by returning an observable or using rxjs marbles. It might save some time & also it does not require to maintain an extra mock class.
Hope that it is useful and it makes sense.
Solution 3
I have figured out how to do this!
Since ActivatedRoute
is a service, a mock service for it can be established. Let's call this mock service MockActivatedRoute
. We will extend ActivatedRoute
in MockActivatedRoute
, as follows:
class MockActivatedRoute extends ActivatedRoute {
constructor() {
super(null, null, null, null, null);
this.params = Observable.of({id: "5"});
}
The line super(null, ....)
initializes the super class, which has four mandatory parameters. However, in this instance, we need nothing from any of these parameters, so we initialize them to null
values. All we need is the value of params
which is an Observable<>
. Therefore, with this.params
, we override the value of params
and initialize it to be the Observable<>
of the parameter on which the test subject is relying.
Then, as any other mock service, just initialize it and override the provider for the component.
Good luck!
Solution 4
Here is how I tested it in angular 2.0 latest...
import { ActivatedRoute, Data } from '@angular/router';
and in Providers section
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
yourData: 'yolo'
})
}
}
}
Solution 5
Just add a mock of the ActivatedRoute:
providers: [
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
]
...
class MockActivatedRoute {
// here you can add your mock objects, like snapshot or parent or whatever
// example:
parent = {
snapshot: {data: {title: 'myTitle ' } },
routeConfig: { children: { filter: () => {} } }
};
}
Related videos on Youtube
Colby Cox
Updated on July 08, 2022Comments
-
Colby Cox almost 2 years
I am unit testing a component that is used to edit an object. The object has an unique
id
that is used in order to grab the specific object from an array of objects that are hosted in a service. The specificid
is procured through a parameter that is passed via routing, specifically through theActivatedRoute
class.The constructor is as follows:
constructor(private _router:Router, private _curRoute:ActivatedRoute, private _session:Session) {} ngOnInit() { this._curRoute.params.subscribe(params => { this.userId = params['id']; this.userObj = this._session.allUsers.filter(user => user.id.toString() === this.userId.toString())[0];
I want to run basic unit tests on this component. However, I am not sure as to how I can inject the
id
parameter, and the component needs this parameter.By the way: I already have a mock for the
Session
service, so no worries there. -
Aarmora almost 8 yearsI am facing this right now! However, I'm getting errors when I try to use
super
orObservable
. Where do these come from? -
Michael JDI over 7 yearsCan you provide the complete code for the providers section?
-
Rady over 7 yearsThis is a complete unit test class. plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue
-
Alejandro Sanz Díaz over 7 yearsObservable.of does not exists for me! :S
-
zmanc over 7 yearsImport Observable from rxjs/Observable
-
shiva over 7 yearsHow do you test unsubscribe in ngOnDestroy
-
Quovadisqc about 7 yearsThis will break in a real life usecase because you aren't returning a subscription and you won't be able to use call .unsubscribe() in ngOnDestroy.
-
Quovadisqc about 7 yearsdata: Observable.of({yourData: 'yolo'}) would work though.
-
oooyaya about 7 years
super()
is built in.Observable
is fromrxjs/Observable
or justrxjs
depending on your version. You'd get it usingimport {Observable} from 'rxjs'
. -
mixalbl4 over 6 yearsThis code makes this error in my project:
Uncaught NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'ng:///DynamicTestModule/HomeContentComponent.ngfactory.js'. at http://localhost:9876/_karma_webpack_/polyfills.bundle.js:2605
-
a3uge over 6 years@MixerOID this is chrome eating bugs - put the test in a try-catch and you should see the error.
-
adamdabb over 6 yearsI know its 2 years later... but if you're getting the
Uncaught NetworkError
error that MixerOID posted, make sure you're using the right type of params. My issue was that my code was expectingqueryParams
, notparams
. -
danwellman about 6 yearsTo avoid the generic
Uncaught NetworkError...
, you can also disable sourcemaps by running tests withng test --sourcemaps=false
- then you should see the real error causing the issue -
Michael Dausmann about 6 years{ provide: ActivatedRoute, useValue: { snapshot: { params: Observable.of({userName: 'testUser', emailValidationToken: 'token'}) } } }
-
ThetaSinner almost 6 yearsI agree with @Quovadisqc that you should NOT be mocking classes outside your program. This is a good solution when you replace the subscribe stuff with an Observable.of
-
Ben Racicot over 5 yearsRxJs 6
of
should be used alone. Also you'd likely useRouterTestingModule
instead of this answer's code. -
zmanc over 5 years@BenRacicot this answer was given before RxJs 6 existed. Also instead saying "do this instead" provide an answer that can be upvoted directly.
-
ruffin over 4 yearsYou've accepted one answer and posted another... if this was Highlander (and there could only be one), which one did you "really" pick and why? That is, I think this essentially reduces to the same thing as zmanc's answer, which you accepted. Did you find additional value from setting up this [slightly] more complicated mock?
-
crackmigg over 4 yearsSo much better than having to maintain an extra mock and you can easily set different parameters in tests. Thank you!
-
speksy about 4 yearsThis helps. Do you know how to spy on different params: const dirName = this.route.snapshot.paramMap.get('dirName'); const actionType = this.route.snapshot.paramMap.get('actionType'); On which of bot will spy spyOn(route.snapshot.paramMap, 'get') ? Can I specify key to listen ?
-
dimitris maf about 4 yearsAs I mention above, I think you could use spyOnProperty instead of spyOn, for example spyOnProperty(route.snapshot.paramMap.get, 'dirName'). If I haven't answered your question completely, don't hesitate to tell me. Thanks.
-
rosiejaneenomar over 2 yearsit's working, i just dont know why it is not working when i only put activatedRoute.snapshot and fixture.detect.. in the it . but when i copied your code its working
-
rosiejaneenomar over 2 yearsdo you have any idea, why do i have to initialize again the fixture and component in it block even if it has already in the beforeeach?
-
Ahmed Shehatah over 2 yearsThank you , this was informative and helpful ☺
-
walkeros almost 2 yearsThis works with Angular 6