Angular 2最终释放路由器单元测试

时间:2021-09-07 13:02:37

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

如何使用karma和jasmine对Angular 2.0.0版中的路由器进行单元测试?

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

这是我的旧单元测试在版本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 个解决方案

#1


75  

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

为了测试,我们现在使用TestBed创建一个测试模块。我们可以使用TestBed#configureTestingModule并将元数据对象传递给它,就像我们传递给@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)

对于路由,我们不使用普通的RouterModule,而是使用RouterTestingModule。这将设置路由器和位置,因此您不需要自己。您也可以通过调用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.

除了我们不需要显式调用done之外,上面的异步使用就像完成一样。所有异步任务完成后,Angular实际上都会为我们做这件事。

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.

除非你真的想测试一些路由,否则这可能是首选的方法。无需设置任何路由。在单元测试中,如果您使用实际路由,则会涉及不必要的副作用,这些副作用可能会影响您实际尝试测试的内容,这只是组件的行为。组件的行为是简单地调用导航方法。它不需要测试路由器是否正常工作。 Angular已经保证了这一点。

#2


0  

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

在TestBed.configureTestingModule中配置如下

providers : [MyService]

then create get service in foreach

然后在foreach中创建get服务

myService= TestBed.get(MyService);

update some data from service like

从服务中更新一些数据

myService.someMethodCall();

This way you can play after some data rendering happen.

这样,您可以在一些数据渲染发生后播放。

#3


0  

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

您可以在提供程序中使用useClass,而不是将useValue用于routerStub,它确实对我有用。

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

并且在beforeEach中只是实例化routerStub对象

routerStub = new RouterStub()    

And in the test cases

而在测试用例中

component.router.navigateByUrl('/test');
fixture.detectChanges();

#1


75  

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

为了测试,我们现在使用TestBed创建一个测试模块。我们可以使用TestBed#configureTestingModule并将元数据对象传递给它,就像我们传递给@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)

对于路由,我们不使用普通的RouterModule,而是使用RouterTestingModule。这将设置路由器和位置,因此您不需要自己。您也可以通过调用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.

除了我们不需要显式调用done之外,上面的异步使用就像完成一样。所有异步任务完成后,Angular实际上都会为我们做这件事。

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.

除非你真的想测试一些路由,否则这可能是首选的方法。无需设置任何路由。在单元测试中,如果您使用实际路由,则会涉及不必要的副作用,这些副作用可能会影响您实际尝试测试的内容,这只是组件的行为。组件的行为是简单地调用导航方法。它不需要测试路由器是否正常工作。 Angular已经保证了这一点。

#2


0  

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

在TestBed.configureTestingModule中配置如下

providers : [MyService]

then create get service in foreach

然后在foreach中创建get服务

myService= TestBed.get(MyService);

update some data from service like

从服务中更新一些数据

myService.someMethodCall();

This way you can play after some data rendering happen.

这样,您可以在一些数据渲染发生后播放。

#3


0  

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

您可以在提供程序中使用useClass,而不是将useValue用于routerStub,它确实对我有用。

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

并且在beforeEach中只是实例化routerStub对象

routerStub = new RouterStub()    

And in the test cases

而在测试用例中

component.router.navigateByUrl('/test');
fixture.detectChanges();