How can I test routerLinkActive in angular
Asked Answered
F

2

11

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.

Farrar answered 10/7, 2017 at 13:25 Comment(1)
I'm having the same issue, did you solve it?Gestate
P
11

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');
  }));
})
Puckett answered 12/12, 2018 at 14:42 Comment(1)
this answer should be accepted. It works fine in Angular7. i.imgur.com/tIZ0vVI.pngLemmy
M
0

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:

Some minor updates:

  • Using 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.
  • Using Jasmine's withContext. Calling toBe with failure message as second argument is deprecated. Available since 3.3.0
  • Running navigation in 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.
Monopetalous answered 20/5 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.