Angular2 RC.1 - 将路由器注入单元测试

Angular2 RC.1 - Inject Router into unit test

我正在编写我的 ng2 测试,但我 运行 遇到了一些麻烦,无法将 Router 注入我的组件以进行测试。我的组件的构造函数只接受一个参数——private router: Router.

但是,当我 运行 我的测试用例时,我在尝试注入路由器时遇到错误。我究竟做错了什么?任何人都可以提供一个工作示例吗?

我正在使用 angular2-RC.1

这是我遇到的错误: No provider for ComponentResolver! (Router -> ComponentResolver)

这是我的测试:

import {describe, it, expect, beforeEach, afterEach, beforeEachProviders, inject} from "@angular/core/testing";
import {ReflectiveInjector, provide} from "@angular/core";
import {HTTP_PROVIDERS} from "@angular/http";
import {Router, ROUTER_PROVIDERS} from "@angular/router";
import {ROUTER_FAKE_PROVIDERS} from "@angular/router/testing";
import {Location} from "@angular/common";
import {SpyLocation} from "@angular/common/testing/location_mock";
import {Observable} from "rxjs/Observable";
import {MyComp} from "./MyComp";

describe("MyComp", () => {

  let injector: ReflectiveInjector,
      myComp: MyComp,
      router: Router;

  beforeEach(() => {

    injector = ReflectiveInjector.resolveAndCreate([         
      HTTP_PROVIDERS,
      ROUTER_FAKE_PROVIDERS,          
      provide(Location, {useClass: SpyLocation})

    ]);

    router = injector.get(Router);
    myComp = new MyComp(router);


  });    

  afterEach(() => {
    injector = null;    
    myComp = null;
    router = null; 
  });

  it("should be defined", () => {

    expect(myComp).toBeDefined();

  });



});

一些修改...这个简单的解决方案对我有用:

import {MyComp} from "./MyComp";
import {RootRouter} from 'angular2/src/router/router';
import {provide} from 'angular2/core'; 
import {Router} from 'angular2/router';

describe("MyComp", () => {
  let myComp: MyComp,
  router;

beforeEach(() => {
  provide(Router, {useClass: RootRouter})
  myComp = new MyComp(router);
}) 

it("should be defined", () => {
  expect(myComp).toBeDefined();
  });
});

好吧,我想到了一个解决方案。这并不理想,但它确实有效。基本上,我正在创建一个 MockRouter class 来实现我需要的方法。

模拟路由器:

export class MockRouter {

    public navigate() {
        console.log(“Mock router was called.”);
    }

}

现在,在我的测试用例中,我所要做的就是为路由器提供模拟实现:

provide(Router, {useClass: MockRouter})

如果 NG2 文档能够展示如何正确地将 Router 注入到 Jasmine 测试用例中,真的 会很好。模拟他们的对象似乎应该是一个不必要的步骤。

(仅供参考,我尝试使用 ROUTER_FAKE_PROVIDERS,但仍然出现上面的 ComponentResolver 错误)

您需要为路由器创建一个 jasmine 间谍对象。 AppComponent 有一个接受 Router 的构造函数。

import { AppComponent } from './app.component';
import {Router, ROUTER_PROVIDERS} from "@angular/router";

describe('app component', () => {
    let component: AppComponent;
    let router: Router;
    beforeAll(() => {
        router = jasmine.createSpyObj("Router", ['navigate']);
        component = new AppComponent(router);
    });
    it("should be defined", () => {
        expect(component).toBeDefined();
    });
});

这是一个替代解决方案,它有点冗长但 允许我们使用 SpyLocation 来检查路线变化。 首先我们创建通用测试路由器提供程序。

路由器测试-providers.ts

import { ComponentResolver } from '@angular/core';
import { Type } from '@angular/core/src/facade/lang';
import { SpyLocation } from '@angular/common/testing';
import { Location } from '@angular/common';
import { Router, RouterOutletMap } from '@angular/router';
import { RouteSegment } from '@angular/router/src/segments';
import { RouterUrlSerializer, DefaultRouterUrlSerializer } from '@angular/router/src/router_url_serializer';
/**
 * this class provides the means of loading the tested component type
 */
export class FakeRootComponentLoader {
    constructor(private rootComponentType: Type) {
      this.rootComponentType = rootComponentType;
    }
    public getRootComponentType = () => {
        return this.rootComponentType;
    }
 }

let routerFactory = function (
        fakeRootComponentLoader: FakeRootComponentLoader,
        componentResolver: ComponentResolver,
        urlSerializer: RouterUrlSerializer,
        routerOutletMap: RouterOutletMap,
        location: Location): Router
{
  let fakeRootComponentType = fakeRootComponentLoader.getRootComponentType();
  /**
   * _rootComponent should not be null, but it is what in angular2 rc.1 code
   * so we replicate the behaviour
   */
  return new Router(
      null,
      fakeRootComponentType,
      componentResolver,
      urlSerializer,
      routerOutletMap,
      location);
};

export const ROUTER_TEST_PROVIDERS: any[] = [
  {provide: RouterUrlSerializer, useClass: DefaultRouterUrlSerializer},
  RouterOutletMap,
  {provide: Location, useClass: SpyLocation},
  {provide: RouteSegment, useFactory: (r) => r.routeTree.root, deps: [Router]},
  {
    provide: Router,
    useFactory: routerFactory,
    deps: [FakeRootComponentLoader, ComponentResolver, RouterUrlSerializer, RouterOutletMap, Location]
  }
];

下面提供相应的jasmine测试。

navigation.spec.ts

import { Component } from '@angular/core';
import { beforeEach, beforeEachProviders, inject } from '@angular/core/testing';
import { ROUTER_DIRECTIVES, Route, Routes, Router } from '@angular/router';
import { TestComponentBuilder } from '@angular/compiler/testing';
import { Location } from '@angular/common';
import { ROUTER_TEST_PROVIDERS, FakeRootComponentLoader } from './router-test-providers';
/**
 * We inject router into the EmptyComponent,
 * Due to the way DI works in angular2, if we import the ROUTER_TEST_PROVIDERS, 
 * and inject the Router, we will get our own implementation of the Router injected.
 */
@Component({selector: 'empty-component', template: `empty`})
@Component({
  selector: 'empty-component',
  template: `empty`,
  directives: [ROUTER_DIRECTIVES]
})
class EmptyComponent {
    constructor (private router: Router){ }
    public getRouter() {return this.router;}
}

@Component({
  selector: 'root-component',
  template: `<router-outlet></router-outlet>`,
  directives: [ROUTER_DIRECTIVES]
})
@Routes([new Route({path: '/login', component: EmptyComponent})])
class RootComponent { }
describe('navigation', () => {
    beforeEachProviders(() => [
        {
            provide: FakeRootComponentLoader,
            useFactory: () => new FakeRootComponentLoader(RootComponent)
        },
        ROUTER_TEST_PROVIDERS,
        EmptyComponent
    ]);


    let location: Location;
    let testCb: TestComponentBuilder;
    let emptyComp: EmptyComponent;
    beforeEach(inject([Location, TestComponentBuilder, EmptyComponent], (loc, tcb, emptyCt) => {
        location = loc;
        testCb = tcb;
        emptyComp = emptyCt;

    }));

    it('should be defined', () => {
        expect(EmptyComponent).toBeDefined();
    });

    it('Should navigate to login', (done) => {
        expect(location.path()).toEqual('');
        testCb.createAsync(RootComponent).then(fixture => {
            emptyComp.getRouter().navigate(['login']).then(() => {
                fixture.detectChanges();
                expect(location.path()).toBe('/login');
                done();
            }).catch(e => done.fail(e));
        });
    });
});

使用匿名 ES 6 class and jasmine:

beforeEach(() => addProviders([
{ 
    provide: Router, 
    useClass: class { navigate = jasmine.createSpy("navigate"); }
}]));

经过无数次无效的建议: 这是在 Angular4 和 Karma 中对我有用的一种解决方案:

它基于 Andriy Tolstoy 的回答并通读了 Angular 手册。

//ToBeTestedComponent.spec.ts

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ ToBeTestedComponent],
      providers: [
        {
          provide: Router,
          useClass: class { navigate = jasmine.createSpy("navigate"); }
        }
      ]
    })
    .compileComponents();
  }));