Recently I was giving a tutorial on Angular to a friend. In the conversation we landed on the area of testing, one of my favorites. We had written some code together and I spoke a bit about the importance of testing and TDD. As I looked for easy examples, particularly around functionality like Angular Router, I realized that a lot of the examples I ran into in Angular’s own documentation, as well as sites like StackOverflow, were overly complicated for the first time tester.
What I was looking for was some very basic code that could serve as a template for writing future tests, as well as something that was easily understood for teaching. I decided to create an Angular project with my own ideas around easy testing. Those can be found in this GitHub repo.
This article focuses on setting up simple ActivatedRoute tests. For this we will split out out our ActivatedRouteStub to make it reusable through our code. I use the sample provided by Angular itself on GitHub.
import { convertToParamMap, ParamMap, Params, ActivatedRoute } from '@angular/router';
import { ReplaySubject } from 'rxjs';
/**
* 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));
}
}
This gives us a Class to instantiate, and creates our paramMap. Again, the idea is to do things simply, and give us a framework to expand as we become more comfortable with writing tests. Since this is made to be reusable, I would create a “test stubs” or “testing” folder to put this in.
In our component example, we inject an ActivatedRoute into our constructor. Remember, this represents our production code. In ngOnInit, for this example, we subscribe to our activatedRoute. I’ve left the fat arrow function blank. As you’re playing with this example, you can print the paramMap, or write a service that uses data from the paramMap to get an idea how the data is injected into our code from the test.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
@Component({
selector: 'app-test-comp',
templateUrl: './test-comp.component.html',
styleUrls: ['./test-comp.component.css']
})
export class TestCompComponent implements OnInit {
constructor( private activatedRoute: ActivatedRoute) { }
ngOnInit() {
this.activatedRoute.paramMap.subscribe(( paramMap: ParamMap) => { });
}
}
The final bit is actually using the stub above. This is where I have found the examples overly complicated. Often we will see deeper expressions of objects written directly into the provides block. The other area where I find examples lacking is that they will only show short blocks of code, and leave out important bits (like instantiating your new ActivatedRoute). Below, I have included the entire spec file contents. I labeled each ActivatedRoute test addition with a comment in the line above. The interesting bits are that you instantiate a new activatedRoute variable in your beforeEach clause. The parameter to this would be whatever data you need in your paramMap. You can build that object directly in the call to New, you can add a variable with a test object that’s used as the parameter, or you can write some deeper code in another file that can be more dynamic with your test data. An example of that would be creating multiple objects with different ID’s or other data.
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TestCompComponent } from './test-comp.component';
// These are added for our ActivatedRoute test
import { ActivatedRouteStub } from '../testing/activated-route-stub';
import { ActivatedRoute } from '@angular/router';
describe('TestCompComponent', () => {
let component: TestCompComponent;
let fixture: ComponentFixture<TestCompComponent>;
// This was added for our ActivatedRoute test
let activatedRoute: ActivatedRouteStub;
beforeEach(async(() => {
// This was added for our ActivatedRoute test
activatedRoute = new ActivatedRouteStub({ this: 'that' });
TestBed.configureTestingModule({
declarations: [TestCompComponent],
// This was added for our ActivatedRoute test
// Notice how it uses the "value" from activatedRoute. If you console.log this
// you'll see that it's an observable.
providers: [{ provide: ActivatedRoute, useValue: activatedRoute }]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestCompComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
// Showing what we have in activatedRoute now
// along with subscribing to our paramMap and printing it. If you
// do this in the test-comp.component's ngOnInit, you'll see the same
// info. In this case a paramMap with a params node { this: 'that' }
console.log(activatedRoute);
activatedRoute.paramMap.subscribe(paramMap => console.log(paramMap));
expect(component).toBeTruthy();
});
});
That’s it! Some simple template code to get you started testing ActivatedRoute. While this might be too simplistic for actual production testing, this bit of code should get you over the initial hump of learning how to get started.