Angular 2 Final Release Router Unit Test

2019-01-13 20:27发布

How do I unit test routers in Angular version 2.0.0 with karma and jasmine?

Here's what my old unit test looks like in version 2.0.0-beta.14

import {
  it,
  inject,
  injectAsync,
  beforeEach,
  beforeEachProviders,
  TestComponentBuilder
} from 'angular2/testing';

import { RootRouter } from 'angular2/src/router/router';
import { Location, RouteParams, Router, RouteRegistry, ROUTER_PRIMARY_COMPONENT } from 'angular2/router';
import { SpyLocation } from 'angular2/src/mock/location_mock';
import { provide } from 'angular2/core';

import { App } from './app';

describe('Router', () => {

  let location, router;

  beforeEachProviders(() => [
    RouteRegistry,
    provide(Location, {useClass: SpyLocation}),
    provide(Router, {useClass: RootRouter}),
    provide(ROUTER_PRIMARY_COMPONENT, {useValue: App})
  ]);

  beforeEach(inject([Router, Location], (_router, _location) => {
    router = _router;
    location = _location;
  }));

  it('Should be able to navigate to Home', done => {
    router.navigate(['Home']).then(() => {
      expect(location.path()).toBe('');
      done();
    }).catch(e => done.fail(e));
  });

});

3条回答
smile是对你的礼貌
2楼-- · 2019-01-13 21:09

Instead of using useValue for routerStub, you can use useClass in the providers and it really worked for me.

export class RouterStub {
  public url: string = '/';
  constructor() { }
    enter code here
  navigateByUrl(url: any) {
    this.url = url;
  }
}

And in the beforeEach just instantiate the routerStub object like

routerStub = new RouterStub()    

And in the test cases

component.router.navigateByUrl('/test');
fixture.detectChanges();
查看更多
再贱就再见
3楼-- · 2019-01-13 21:18

Good approach suggested by Paul I have also configure my routing same way but additionally I have added service to update some data for routing and then check for current location.

so you can add service to update data on component which render some data and then will check about navigation.

configure below in TestBed.configureTestingModule

providers : [MyService]

then create get service in foreach

myService= TestBed.get(MyService);

update some data from service like

myService.someMethodCall();

This way you can play after some data rendering happen.

查看更多
放我归山
4楼-- · 2019-01-13 21:29

For testing we now create a testing module using TestBed. We can use the TestBed#configureTestingModule and pass a metadata object to it the same way we would pass to @NgModule

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [ /* modules to import */ ],
    providers: [ /* add providers */ ],
    declarations: [ /* components, directives, and pipes */ ]
  });
});

For routing, instead of using the normal RouterModule, we would instead use RouterTestingModule. This sets up the Router and Location, so you don't need to yourself. You can also pass routes to it, by calling RouterTestingModule.withRoutes(Routes)

TestBed.configureTestingModule({
  imports: [
    RouterTestingModule.withRoutes([
      { path: 'home', component: DummyComponent }
    ])
  ]
})

To get the Location and Router in the test, the same thing works, as in your example.

let router, location;

beforeEach(() => {
  TestBed...
});

beforeEach(inject([Router, Location], (_router: Router, _location: Location) => {
  router = _router;
  location = _location;
}));

You could also inject into each test as necessary

it('should go home',
    async(inject([Router, Location], (router: Router, location: Location) => {
})));

The async above is used like done except we don't need to explicitly call done. Angular will actually do that for us after all asynchronous tasks are complete.

Another way to get the providers is from the test bed.

let location, router;

beforeEach(() => {
  TestBed.configureTestingModule({
    imports: [RouterTestingModule.withRoutes([
      { path: 'home', component: DummyComponent }
    ])],
  });
  let injector = getTestBed();
  location = injector.get(Location);
  router = injector.get(Router);
});

Here's a complete test, refactoring your example

import { Component } from '@angular/core';
import { Location } from '@angular/common';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { fakeAsync, async, inject, TestBed, getTestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

@Component({
  template: `
    <router-outlet></router-outlet>
  `
})
class RoutingComponent { }

@Component({
  template: ''
})
class DummyComponent { }

describe('component: RoutingComponent', () => {
  let location, router;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule.withRoutes([
        { path: 'home', component: DummyComponent }
      ])],
      declarations: [RoutingComponent, DummyComponent]
    });
  });

  beforeEach(inject([Router, Location], (_router: Router, _location: Location) => {
    location = _location;
    router = _router;
  }));

  it('should go home', async(() => {
    let fixture = TestBed.createComponent(RoutingComponent);
    fixture.detectChanges();
    router.navigate(['/home']).then(() => {
      expect(location.path()).toBe('/home');
      console.log('after expect');
    });
  }));
});

UPDATE

Also, if you want to simply mock the router, which actually might be the better way to go in a unit test, you could simply do

let routerStub;

beforeEach(() => {
  routerStub = {
    navigate: jasmine.createSpy('navigate'),
  };
  TestBed.configureTestingModule({
    providers: [ { provide: Router, useValue: routerStub } ],
  });
});

And in your tests, all you want to do is test that the stub is called with the correct argument, when the component interacts with it

expect(routerStub.navigate).toHaveBeenCalledWith(['/route']);

Unless you actually want to test some routing, this is probably the preferred way to go. No need to set up any routing. In a unit test, if you are using real routing, you're involving unnecessary side effects that could affect what you are really trying to test, which is just the behavior of the component. And the behavior of the component is to simply call the navigate method. It doesn't need to test that the router works. Angular already guarantees that.

查看更多
登录 后发表回答