In my Angular app I need to be able to unit test that routerLink
create the correct href
attribute. Sometimes they are links relative to the current route, and sometimes they are absolute links from the root.
I am only able to successfully test the absolute links. When I attempt to test the relative links it just shows the href
value as '/'
In my component template I do this
@for (item of dataService.list; track item.id){
@if (item.documents.length === 1) {
<a [routerLink]="['/', 'documents', item.documents[0].id]">{{ item.id }} (Single)</a>
} @else {
<a [routerLink]="item.id">{{ item.id }} (Multiple)</a>
}
}
And in my *.spec.ts
file I have
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let mockDataService: DataService;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [provideRouter([]), { provide: ActivatedRoute, useValue: {} }],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
mockDataService = TestBed.inject(DataService);
});
//This test FAILS with "Expected '/' to equal 'One'."
it('should use a relative link when there are multiple documents', () => {
spyOnProperty(mockDataService, 'list', 'get').and.returnValue([
{ id: 'One', documents: [{ id: '1' }, { id: '2' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
expect(linkEl.getAttribute('href')).toEqual('One');
});
//This test PASSES
it('should use an absolute link when there is only one document', () => {
spyOnProperty(mockDataService, 'list').and.returnValue([
{ id: 'Two', documents: [{ id: '3' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
expect(linkEl.getAttribute('href')).toEqual('/documents/3');
});
});
The provided mock ActivatedRoute
was messing up the routerLink
created href
, remove it and the relative paths work great.
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [
provideRouter([]),
// { provide: ActivatedRoute, useValue: {} } // <- changed here!
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
mockDataService = TestBed.inject(DataService);
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
import { AppComponent } from './app.component';
import { ActivatedRoute, provideRouter } from '@angular/router';
import { By } from '@angular/platform-browser';
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let mockDataService: DataService;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [
provideRouter([]),
// { provide: ActivatedRoute, useValue: {} } // <- changed here!
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
mockDataService = TestBed.inject(DataService);
});
it('should use a relative link when there are multiple documents', () => {
spyOnProperty(mockDataService, 'list', 'get').and.returnValue([
{ id: 'One', documents: [{ id: '1' }, { id: '2' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
console.log(linkEl);
expect(linkEl.getAttribute('href')).toEqual('/One');
});
it('should use an absolute link when there is only one document', () => {
spyOnProperty(mockDataService, 'list').and.returnValue([
{ id: 'Two', documents: [{ id: '3' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
console.log('absolute link', linkEl.getAttribute('href'));
expect(linkEl.getAttribute('href')).toEqual('/documents/3');
});
});
provideRouter
:We can first add RouterOutlet
and the router-outlet selector.
Then create dummy routes with dummy components to provide the fake routes. Then we need to get rid of the activated route mock, since it is messing up the routing.
@Component({ selector: 'dummy' })
export class DummyComponent {}
@Component({ selector: 'dummy2' })
export class DummyComponent2 {}
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let mockDataService: DataService;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [
provideRouter([
{ path: 'documents/:id', component: DummyComponent },
{ path: ':id', component: DummyComponent2 },
]),
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
mockDataService = TestBed.inject(DataService);
});
Now we simply click the a tag and check the route.
it('should use a relative link when there are multiple documents', async () => {
spyOnProperty(mockDataService, 'list', 'get').and.returnValue([
{ id: 'One', documents: [{ id: '1' }, { id: '2' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
linkEl.click();
await fixture.whenStable();
expect(TestBed.inject(Router).url).toEqual('/One');
});
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DataService } from './data.service';
import { AppComponent } from './app.component';
import { ActivatedRoute, provideRouter, Router } from '@angular/router';
import { By } from '@angular/platform-browser';
import { Component } from '@angular/core';
@Component({ selector: 'dummy' })
export class DummyComponent {}
@Component({ selector: 'dummy2' })
export class DummyComponent2 {}
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let mockDataService: DataService;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
providers: [
provideRouter([
{ path: 'documents/:id', component: DummyComponent },
{ path: ':id', component: DummyComponent2 },
]),
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
mockDataService = TestBed.inject(DataService);
});
it('should use a relative link when there are multiple documents', async () => {
spyOnProperty(mockDataService, 'list', 'get').and.returnValue([
{ id: 'One', documents: [{ id: '1' }, { id: '2' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
linkEl.click();
await fixture.whenStable();
expect(TestBed.inject(Router).url).toEqual('/One');
});
it('should use an absolute link when there is only one document', async () => {
spyOnProperty(mockDataService, 'list').and.returnValue([
{ id: 'Two', documents: [{ id: '3' }] },
]);
fixture.detectChanges();
const linkEl = fixture.debugElement.query(By.css('a')).nativeElement;
linkEl.click();
await fixture.whenStable();
expect(TestBed.inject(Router).url).toEqual('/documents/3');
});
});