Angular 2 Jasmine 无法绑定到 'routerLink',因为它不是 'a' 的已知 属性
Angular 2 Jasmine Can't bind to 'routerLink' since it isn't a known property of 'a'
我正在为我的导航栏组件创建单元测试,但出现错误:
Can't bind to 'routerLink' since it isn't a known property of 'a'
导航栏组件 TS
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { NavActiveService } from '../../../services/navactive.service';
import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
@Component({
moduleId: module.id,
selector: 'my-navbar',
templateUrl: 'navbar.component.html',
styleUrls:['navbar.component.css'],
providers: [NavActiveService]
})
export class NavComponent {
showNavBar: boolean = true;
constructor(private router: Router,
private navactiveservice:NavActiveService,
private globalEventsManager: GlobalEventsManager){
this.globalEventsManager.showNavBar.subscribe((mode:boolean)=>{
this.showNavBar = mode;
});
}
}
导航栏组件规范
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NavComponent } from './navbar.component';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
export function main() {
describe('Navbar component', () => {
let de: DebugElement;
let comp: NavComponent;
let fixture: ComponentFixture<NavComponent>;
let router: Router;
// preparing module for testing
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NavComponent],
}).compileComponents().then(() => {
fixture = TestBed.createComponent(NavComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css('p'));
});
}));
it('should create component', () => expect(comp).toBeDefined());
/* it('should have expected <p> text', () => {
fixture.detectChanges();
const h1 = de.nativeElement;
expect(h1.innerText).toMatch(" ");
});*/
});
}
我意识到我需要将路由器添加为间谍,但如果我将其添加为 SpyObj 并将其声明为提供者,我会遇到同样的错误。
我有没有更好的方法来修复这个错误?
编辑:工作单元测试
根据答案构建此单元测试:
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NavComponent } from './navbar.component';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { RouterLinkStubDirective, RouterOutletStubComponent } from '../../../../test/router-stubs';
import { Router } from '@angular/router';
import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared.module';
export function main() {
let comp: NavComponent;
let fixture: ComponentFixture<NavComponent>;
let mockRouter:any;
class MockRouter {
//noinspection TypeScriptUnresolvedFunction
navigate = jasmine.createSpy('navigate');
}
describe('Navbar Componenet', () => {
beforeEach( async(() => {
mockRouter = new MockRouter();
TestBed.configureTestingModule({
imports: [ SharedModule ]
})
// Get rid of app's Router configuration otherwise many failures.
// Doing so removes Router declarations; add the Router stubs
.overrideModule(SharedModule, {
remove: {
imports: [ RouterModule ],
},
add: {
declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ],
providers: [ { provide: Router, useValue: mockRouter }, GlobalEventsManager ],
}
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(NavComponent);
comp = fixture.componentInstance;
});
}));
tests();
});
function tests() {
let links: RouterLinkStubDirective[];
let linkDes: DebugElement[];
beforeEach(() => {
// trigger initial data binding
fixture.detectChanges();
// find DebugElements with an attached RouterLinkStubDirective
linkDes = fixture.debugElement
.queryAll(By.directive(RouterLinkStubDirective));
// get the attached link directive instances using the DebugElement injectors
links = linkDes
.map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
});
it('can instantiate it', () => {
expect(comp).not.toBeNull();
});
it('can get RouterLinks from template', () => {
expect(links.length).toBe(5, 'should have 5 links');
expect(links[0].linkParams).toBe( '/', '1st link should go to Home');
expect(links[1].linkParams).toBe('/', '2nd link should go to Home');
expect(links[2].linkParams).toBe('/upload', '3rd link should go to Upload');
expect(links[3].linkParams).toBe('/about', '4th link should to to About');
expect(links[4].linkParams).toBe('/login', '5th link should go to Logout');
});
it('can click Home link in template', () => {
const uploadLinkDe = linkDes[1];
const uploadLink = links[1];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/');
});
it('can click upload link in template', () => {
const uploadLinkDe = linkDes[2];
const uploadLink = links[2];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/upload');
});
it('can click about link in template', () => {
const uploadLinkDe = linkDes[3];
const uploadLink = links[3];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/about');
});
it('can click logout link in template', () => {
const uploadLinkDe = linkDes[4];
const uploadLink = links[4];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/login');
});
}
}
Angular Testing docs 通过使用 RouterLinkDirectiveStub
和 RouterOutletStubComponent
来解决这个问题,因此 routerLink
是 一个已知的 属性 <a>
个。
基本上它说使用 RouterOutletStubComponent
是测试 routerLinks
的安全方法,没有使用真正的 RouterOutlet
的所有复杂性和错误。您的项目需要知道它的存在,这样它就不会抛出错误,但在这种情况下实际上不需要做任何事情。
RouterLinkDirectiveStub
使您能够单击带有 routerLink
指令的 <a>
链接,并获得足够的信息来测试它是否被单击 (navigatedTo) 并转到正确的路线(链接参数)。任何比这更多的功能,你真的不再孤立地测试你的组件。
看看他们在 app/app.component.spec.ts
中的 Tests Demo。抓住 testing/router-link-directive-stub.ts
并添加到您的项目中。然后,您会将 2 个存根项注入到您的 TestBed 声明中。
只需在 组件 spec.ts 文件
的 TestBed.configureTestingModule
中导入 RouterTestingModule
例如:
import { RouterTestingModule } from '@angular/router/testing';
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [ ComponentHeaderComponent ]
})
如果你只想单独测试而不关心模板,你可以添加NO_ERRORS_SCHEMA。这告诉 Angular 如果在 HTML
中遇到任何未知的属性或元素,则不要显示错误
例如:
TestBed.configureTestingModule({
declarations: [ ComponentHeaderComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
})
我正在为我的导航栏组件创建单元测试,但出现错误:
Can't bind to 'routerLink' since it isn't a known property of 'a'
导航栏组件 TS
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { NavActiveService } from '../../../services/navactive.service';
import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
@Component({
moduleId: module.id,
selector: 'my-navbar',
templateUrl: 'navbar.component.html',
styleUrls:['navbar.component.css'],
providers: [NavActiveService]
})
export class NavComponent {
showNavBar: boolean = true;
constructor(private router: Router,
private navactiveservice:NavActiveService,
private globalEventsManager: GlobalEventsManager){
this.globalEventsManager.showNavBar.subscribe((mode:boolean)=>{
this.showNavBar = mode;
});
}
}
导航栏组件规范
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NavComponent } from './navbar.component';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Router } from '@angular/router';
export function main() {
describe('Navbar component', () => {
let de: DebugElement;
let comp: NavComponent;
let fixture: ComponentFixture<NavComponent>;
let router: Router;
// preparing module for testing
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NavComponent],
}).compileComponents().then(() => {
fixture = TestBed.createComponent(NavComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css('p'));
});
}));
it('should create component', () => expect(comp).toBeDefined());
/* it('should have expected <p> text', () => {
fixture.detectChanges();
const h1 = de.nativeElement;
expect(h1.innerText).toMatch(" ");
});*/
});
}
我意识到我需要将路由器添加为间谍,但如果我将其添加为 SpyObj 并将其声明为提供者,我会遇到同样的错误。
我有没有更好的方法来修复这个错误?
编辑:工作单元测试
根据答案构建此单元测试:
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { NavComponent } from './navbar.component';
import { DebugElement } from '@angular/core';
import { By } from '@angular/platform-browser';
import { RouterLinkStubDirective, RouterOutletStubComponent } from '../../../../test/router-stubs';
import { Router } from '@angular/router';
import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared.module';
export function main() {
let comp: NavComponent;
let fixture: ComponentFixture<NavComponent>;
let mockRouter:any;
class MockRouter {
//noinspection TypeScriptUnresolvedFunction
navigate = jasmine.createSpy('navigate');
}
describe('Navbar Componenet', () => {
beforeEach( async(() => {
mockRouter = new MockRouter();
TestBed.configureTestingModule({
imports: [ SharedModule ]
})
// Get rid of app's Router configuration otherwise many failures.
// Doing so removes Router declarations; add the Router stubs
.overrideModule(SharedModule, {
remove: {
imports: [ RouterModule ],
},
add: {
declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ],
providers: [ { provide: Router, useValue: mockRouter }, GlobalEventsManager ],
}
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(NavComponent);
comp = fixture.componentInstance;
});
}));
tests();
});
function tests() {
let links: RouterLinkStubDirective[];
let linkDes: DebugElement[];
beforeEach(() => {
// trigger initial data binding
fixture.detectChanges();
// find DebugElements with an attached RouterLinkStubDirective
linkDes = fixture.debugElement
.queryAll(By.directive(RouterLinkStubDirective));
// get the attached link directive instances using the DebugElement injectors
links = linkDes
.map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
});
it('can instantiate it', () => {
expect(comp).not.toBeNull();
});
it('can get RouterLinks from template', () => {
expect(links.length).toBe(5, 'should have 5 links');
expect(links[0].linkParams).toBe( '/', '1st link should go to Home');
expect(links[1].linkParams).toBe('/', '2nd link should go to Home');
expect(links[2].linkParams).toBe('/upload', '3rd link should go to Upload');
expect(links[3].linkParams).toBe('/about', '4th link should to to About');
expect(links[4].linkParams).toBe('/login', '5th link should go to Logout');
});
it('can click Home link in template', () => {
const uploadLinkDe = linkDes[1];
const uploadLink = links[1];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/');
});
it('can click upload link in template', () => {
const uploadLinkDe = linkDes[2];
const uploadLink = links[2];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/upload');
});
it('can click about link in template', () => {
const uploadLinkDe = linkDes[3];
const uploadLink = links[3];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/about');
});
it('can click logout link in template', () => {
const uploadLinkDe = linkDes[4];
const uploadLink = links[4];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/login');
});
}
}
Angular Testing docs 通过使用 RouterLinkDirectiveStub
和 RouterOutletStubComponent
来解决这个问题,因此 routerLink
是 一个已知的 属性 <a>
个。
基本上它说使用 RouterOutletStubComponent
是测试 routerLinks
的安全方法,没有使用真正的 RouterOutlet
的所有复杂性和错误。您的项目需要知道它的存在,这样它就不会抛出错误,但在这种情况下实际上不需要做任何事情。
RouterLinkDirectiveStub
使您能够单击带有 routerLink
指令的 <a>
链接,并获得足够的信息来测试它是否被单击 (navigatedTo) 并转到正确的路线(链接参数)。任何比这更多的功能,你真的不再孤立地测试你的组件。
看看他们在 app/app.component.spec.ts
中的 Tests Demo。抓住 testing/router-link-directive-stub.ts
并添加到您的项目中。然后,您会将 2 个存根项注入到您的 TestBed 声明中。
只需在 组件 spec.ts 文件
的TestBed.configureTestingModule
中导入 RouterTestingModule
例如:
import { RouterTestingModule } from '@angular/router/testing';
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [ ComponentHeaderComponent ]
})
如果你只想单独测试而不关心模板,你可以添加NO_ERRORS_SCHEMA。这告诉 Angular 如果在 HTML
中遇到任何未知的属性或元素,则不要显示错误例如:
TestBed.configureTestingModule({
declarations: [ ComponentHeaderComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
})