Reputation: 111
I am trying to test routing in the following very simple component:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'side-bar',
templateUrl: `
<div class="sidebar hidden-sm-down">
<div class="sidebar-block" >
<a routerLink="/" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Home</a>
</div>
<div class="sidebar-block">
<a routerLink="/edit" routerLinkActive="active">Edit</a>
</div>
</div>
`,
styleUrls: ['./side-bar.component.scss']
})
export class SideBarComponent implements OnInit {
ngOnInit() { }
}
I currently have:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { RouterLinkStubDirective } from '../../../testing/router-stubs';
import { SideBarComponent } from './side-bar.component';
describe('SideBarComponent', () => {
let component: SideBarComponent;
let fixture: ComponentFixture<SideBarComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ RouterTestingModule ],
declarations: [SideBarComponent, RouterLinkStubDirective ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SideBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('can be instantiated', () => {
expect(component).not.toBeNull();
});
it('can link to main pages', () => {
var linkElements = fixture.debugElement.queryAll(By.directive(RouterLinkStubDirective));
var links = linkElements.map(element => element.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
expect(links.length).toBe(2, 'should have 2 links');
expect(links[0].linkParams).toBe('/', '1st link should go to Home');
expect(links[1].linkParams).toBe('/edit', '2nd link should go to Edit');
});
it('can show the active link', () => {
var activeLinks = fixture.debugElement.queryAll(By.css('.active')).map(element => element.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
expect(activeLinks.length).toBe(0, 'should only have 1 active link');
expect(activeLinks[0].linkParams).toBe('/', 'active link should be for Home');
});
});
The first couple of tests are working and follow the example laid out in the official angular testing documentation with the only departure being that I had to import the RouterTestingModule
so that the routerLinkActiveOptions
wouldn't cause an error to be thrown.
The goal in the final test is to assert that routerLinkActive
is working as expected. I'm not expecting the current implementation to work, ideally I would be able to set the current route and then check that the active link is correctly set in a way similar to the last test.
The documentation around the RouterTestingModule
is non-existent so it may be possible to do it using this. If anyone knows a way to achieve this, help would be greatly appreciated.
Upvotes: 11
Views: 4770
Reputation: 111
To do test like this you need to use a router, there is an example stackblitz!
But there is some issue with invoking router navigation inside test. link to issue!
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { DebugElement, Component } from '@angular/core';
import { RouterLinkStubDirective } from '../testing/router-link-directive-stub';
import { SideBarComponent } from './side-bar.component';
import { Routes, Router } from '@angular/router';
@Component({
template: ``
})
export class HomeComponent { }
@Component({
template: ``
})
export class EditComponent { }
const routes: Routes = [
{
path: '', component: HomeComponent,
},
{
path: 'edit', component: EditComponent
}
];
describe('SideBarComponent', () => {
let component: SideBarComponent;
let fixture: ComponentFixture<SideBarComponent>;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(routes)],
declarations: [SideBarComponent, RouterLinkStubDirective,
HomeComponent, EditComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SideBarComponent);
component = fixture.componentInstance;
router = TestBed.get(Router);
fixture.detectChanges();
});
it('can be instantiated', () => {
expect(component).not.toBeNull();
});
it('can link to main pages', () => {
var linkElements =
fixture.debugElement.queryAll(By.directive(RouterLinkStubDirective));
var links = linkElements.map(element => element.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
expect(links.length).toBe(2, 'should have 2 links');
expect(links[0].linkParams).toBe('/', '1st link should go to Home');
expect(links[1].linkParams).toBe('/edit', '2nd link should go to Edit');
});
it('can show the active link', fakeAsync(() => {
router.navigate(['edit']);
tick();
var activeLinks = fixture.debugElement.queryAll(By.css('.active')).map(element => element.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
expect(activeLinks.length).toBe(1, 'should only have 1 active link');
expect(activeLinks[0].linkParams).toBe('/edit', 'active link should be for Edit');
router.navigate(['']);
tick();
var activeLinks = fixture.debugElement.queryAll(By.css('.active')).map(element => element.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
expect(activeLinks.length).toBe(1, 'should only have 1 active link');
expect(activeLinks[0].linkParams).toBe('/', 'active link should be for Home');
}));
})
Upvotes: 11
Reputation: 1588
Things have change a bit since first answer so updating it here. That's how you can do it using Angular v17.3 (latest at moment of writing this).
You can find a full example app with all code in its GitHub repository
@Component({
template: ``
})
export class DummyComponent {
}
describe('SideBarComponent', () => {
let component: SideBarComponent;
let fixture: ComponentFixture<SideBarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SideBarComponent], // use "imports" if standalone
providers: [provideRouter([
{path: '', component: DummyComponent},
{path: 'edit', component: DummyComponent}
])]
})
.compileComponents()
fixture = TestBed.createComponent(SideBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('can link to main pages', () => {
const anchorElements = fixture.debugElement.queryAll(By.css('a'));
expect(anchorElements.length).withContext('should have 2 links').toBe(2);
expect(anchorElements[0]?.attributes['href']).withContext('1st link should go to Home').toBe('/');
expect(anchorElements[1]?.attributes['href']).withContext('2nd link should go to Edit').toBe('/edit');
});
it('can show the active link', async () => {
let routerTestingHarness: RouterTestingHarness
await fixture.ngZone?.run(async () => routerTestingHarness = await RouterTestingHarness.create('/'))
let activeLinks = fixture.debugElement.queryAll(By.css('.active'))
expect(activeLinks.length).withContext('should only have 1 active link').toBe(1);
expect(activeLinks[0]?.attributes['href']).withContext('active link should be for Home').toBe('/');
await fixture.ngZone?.run(async () => routerTestingHarness.navigateByUrl('/edit'))
activeLinks = fixture.debugElement.queryAll(By.css('.active'))
expect(activeLinks.length).withContext('should only have 1 active link after navigating').toBe(1);
expect(activeLinks[0]?.attributes['href']).withContext('active link should be for Edit').toBe('/edit');
});
});
Most notable updates:
RouterLink
for expectations and to find elements. Look for anchor (a
) elements and check their href
attribute instead. This decouples the test from changes in RouterLink
APIs. Which follows the spirit of this Angular blog post about router testing. Though you may need to use RouterLink
directive. For instance if using button
elements instead of a
elements. Checkout this alternative answer then that follows the OPs intent of using the RouterLink
directive.RouterTestingModule
. It is deprecated. Recommendation is to use the real router insteadRouterTestingHarness
instead. It helps operating the real router in tests. Available since Angular v15.2.0. As stated in an Angular blog post about router testingSome minor updates:
provideRouter
instead of RouterModule.forRoot
. As Angular apps created with Angular CLI v17 and above are standalone. Therefore they use standalone APIs. Router provider APIs are available since Angular v14.2.0 though and can be used with classic module based apps.withContext
. Calling toBe
with failure message as second argument is deprecated. Available since 3.3.0
ngZone
. In order to avoid warning about navigating outside of ngZone
. Though you can run it without that but you'll get a warning in the console.Upvotes: 2