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));
});
});
});
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();
}));
我正在编写我的 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));
});
});
});
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();
}));