是否可以为单元测试模拟自定义 Angular 2 Material SVG 图标?
Is it possible to mock custom Angular 2 Material SVG icons for unit tests?
在我的应用程序的根组件中,我正在为 md-icon
定义自定义 SVG 图标。当对显示自定义图标的组件进行单元测试时,出现错误。看来错误可能是由于我的根组件在我的子单元测试中没有 used/initialized。
有没有办法在设置测试模块时模拟或添加这些自定义图标(或md-icon
)?我会简单地在我正在测试的组件中定义图标,但我知道其他组件也需要它们。
错误:
Uncaught Error: Error in ./CustomerComponent class CustomerComponent - inline template:34:19 caused by: __WEBPACK_IMPORTED_MODULE_4_rxjs_Observable__.Observable.throw is not a function
完整错误:
从模板中删除自定义图标解决了错误。
我的模板正在使用这样的自定义图标:
<md-icon svgIcon="vip">vip</md-icon>
根组件像这样初始化图标:
this.iconRegistry.addSvgIcon(
'vip',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vip.svg') as string,
);
我这样设置测试组件:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: CUSTOMER,
company: COMPANY,
}),
},
},
},
{
provide: UtilityService,
useClass: UtilityServiceMock,
},
// etc...
],
declarations: [
CustomerComponent,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
})
.compileComponents();
}));
版本
- Angular 2.3.0
- Material 2.0.0-beta.1
回答我自己的问题:
在经历了很多 trial/error 项目后,例如模拟 MdIconRegistry
或使用 componentOverride()
等等,但没有成功,我不再在我的测试中使用共享模块。
相反,我使用 class 的模拟版本直接在我的测试模块中声明 MdIcon 组件。
describe(`CustomerComponent`, () => {
let component: CustomerComponent;
let fixture: ComponentFixture<CustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
MdButtonModule,
],
providers: [
OVERLAY_PROVIDERS,
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: customer,
company: COMPANY,
}),
},
params: Observable.of({
customerId: customerId,
}),
},
},
],
declarations: [
CustomerComponent,
// Declare my own version of MdIcon here so that it is available for the CustomerComponent
MdIconMock,
],
});
fixture = TestBed.createComponent(CustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it(`should exist`, () => {
expect(component).toBeTruthy();
});
});
MdIconMock 只是一个匹配选择器的空白 class:
import { Component } from '@angular/core';
@Component({
template: '',
// tslint:disable-next-line
selector: 'md-icon, mat-icon',
})
// tslint:disable-next-line
export class MdIconMock {
}
Note: Due to TSLint rules that specify the prefix/format of class names/selectors I needed to disable TSLint for this mock.
这是一个迟到的答案。以防万一有人遇到这个问题,除了 OP 的解决方案(这也很好)之外还有其他选择:
使用 forRoot() 导入 MaterialModule
TestBed.configureTestingModule({
declarations: [
TestComponent
],
imports: [SharedModule, MaterialModule.forRoot()],
providers: [{ provide: Router, useValue: routerStub }]
});
TestBed.compileComponents();
获取注入的MdIconRegistry和DomSanitizer
let iconRegistry = TestBed.get(MdIconRegistry);
let sanitizer = TestBed.get(DomSanitizer);
像在普通应用中一样配置它们
iconRegistry.addSvgIcon( 'some-icon',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/some-icon.svg'));
我能够使用 overrideModule
method to stub MdIcon. The documentation is sparse but I was able to find a GitHub issue,其中 Angular 团队成员讨论如何覆盖声明。我们的想法是从 MdIconModule
中删除该组件,以便我们可以声明我们自己的模拟图标组件。
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestedComponent ],
imports: [
RouterTestingModule.withRoutes([]),
SharedModule,
],
})
.overrideModule(MdIconModule, {
remove: {
declarations: [MdIcon],
exports: [MdIcon]
},
add: {
declarations: [MockMdIconComponent],
exports: [MockMdIconComponent]
}
})
.compileComponents();
}));
MockMdIconComponent
的定义很简单
@Component({
selector: 'md-icon',
template: '<span></span>'
})
class MockMdIconComponent {
@Input() svgIcon: any;
@Input() fontSet: any;
@Input() fontIcon: any;
}
我使用这种方法是因为我没有单独导入 Material 模块,而且我不希望我的测试必须进行 Http 调用才能获取 svg 图标。 MockMdIconComponent
可以在测试模块中声明,但我选择在模块覆盖中 declare/export 它,以便我可以将对象提取到测试助手中。
基于@Chic 的回答,因为我到处都有图标,所以我制作了一个测试平台补丁:
import {TestBed} from '@angular/core/testing';
import {MatIconModule, MatIcon} from '@angular/material/icon';
import {Component, Input} from '@angular/core';
export function PatchTestBedMatIcons() {
const original = TestBed.configureTestingModule;
TestBed.configureTestingModule = (moduleDef) => {
return original(moduleDef)
.overrideModule(MatIconModule, {
remove: {
declarations: [MatIcon],
exports: [MatIcon]
},
add: {
declarations: [MockMatIconComponent],
exports: [MockMatIconComponent]
}
});
};
}
@Component({
selector: 'mat-icon',
template: '<span></span>'
})
class MockMatIconComponent {
@Input() svgIcon: any = null;
@Input() fontSet: any = null;
@Input() fontIcon: any = null;
}
然后在你的组件测试中简单地:
import {PatchTestBedMatIcons} from 'src/app/patchTestBedIcons';
PatchTestBedMatIcons();
在我的应用程序的根组件中,我正在为 md-icon
定义自定义 SVG 图标。当对显示自定义图标的组件进行单元测试时,出现错误。看来错误可能是由于我的根组件在我的子单元测试中没有 used/initialized。
有没有办法在设置测试模块时模拟或添加这些自定义图标(或md-icon
)?我会简单地在我正在测试的组件中定义图标,但我知道其他组件也需要它们。
错误:
Uncaught Error: Error in ./CustomerComponent class CustomerComponent - inline template:34:19 caused by: __WEBPACK_IMPORTED_MODULE_4_rxjs_Observable__.Observable.throw is not a function
完整错误:
从模板中删除自定义图标解决了错误。
我的模板正在使用这样的自定义图标:
<md-icon svgIcon="vip">vip</md-icon>
根组件像这样初始化图标:
this.iconRegistry.addSvgIcon(
'vip',
this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vip.svg') as string,
);
我这样设置测试组件:
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: CUSTOMER,
company: COMPANY,
}),
},
},
},
{
provide: UtilityService,
useClass: UtilityServiceMock,
},
// etc...
],
declarations: [
CustomerComponent,
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA,
],
})
.compileComponents();
}));
版本
- Angular 2.3.0
- Material 2.0.0-beta.1
回答我自己的问题:
在经历了很多 trial/error 项目后,例如模拟 MdIconRegistry
或使用 componentOverride()
等等,但没有成功,我不再在我的测试中使用共享模块。
相反,我使用 class 的模拟版本直接在我的测试模块中声明 MdIcon 组件。
describe(`CustomerComponent`, () => {
let component: CustomerComponent;
let fixture: ComponentFixture<CustomerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
ReactiveFormsModule,
MdButtonModule,
],
providers: [
OVERLAY_PROVIDERS,
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy('navigate');
},
},
{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
customer: customer,
company: COMPANY,
}),
},
params: Observable.of({
customerId: customerId,
}),
},
},
],
declarations: [
CustomerComponent,
// Declare my own version of MdIcon here so that it is available for the CustomerComponent
MdIconMock,
],
});
fixture = TestBed.createComponent(CustomerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it(`should exist`, () => {
expect(component).toBeTruthy();
});
});
MdIconMock 只是一个匹配选择器的空白 class:
import { Component } from '@angular/core';
@Component({
template: '',
// tslint:disable-next-line
selector: 'md-icon, mat-icon',
})
// tslint:disable-next-line
export class MdIconMock {
}
Note: Due to TSLint rules that specify the prefix/format of class names/selectors I needed to disable TSLint for this mock.
这是一个迟到的答案。以防万一有人遇到这个问题,除了 OP 的解决方案(这也很好)之外还有其他选择:
使用 forRoot() 导入 MaterialModule
TestBed.configureTestingModule({
declarations: [
TestComponent
],
imports: [SharedModule, MaterialModule.forRoot()],
providers: [{ provide: Router, useValue: routerStub }]
});
TestBed.compileComponents();
获取注入的MdIconRegistry和DomSanitizer
let iconRegistry = TestBed.get(MdIconRegistry);
let sanitizer = TestBed.get(DomSanitizer);
像在普通应用中一样配置它们
iconRegistry.addSvgIcon( 'some-icon',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/some-icon.svg'));
我能够使用 overrideModule
method to stub MdIcon. The documentation is sparse but I was able to find a GitHub issue,其中 Angular 团队成员讨论如何覆盖声明。我们的想法是从 MdIconModule
中删除该组件,以便我们可以声明我们自己的模拟图标组件。
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TestedComponent ],
imports: [
RouterTestingModule.withRoutes([]),
SharedModule,
],
})
.overrideModule(MdIconModule, {
remove: {
declarations: [MdIcon],
exports: [MdIcon]
},
add: {
declarations: [MockMdIconComponent],
exports: [MockMdIconComponent]
}
})
.compileComponents();
}));
MockMdIconComponent
的定义很简单
@Component({
selector: 'md-icon',
template: '<span></span>'
})
class MockMdIconComponent {
@Input() svgIcon: any;
@Input() fontSet: any;
@Input() fontIcon: any;
}
我使用这种方法是因为我没有单独导入 Material 模块,而且我不希望我的测试必须进行 Http 调用才能获取 svg 图标。 MockMdIconComponent
可以在测试模块中声明,但我选择在模块覆盖中 declare/export 它,以便我可以将对象提取到测试助手中。
基于@Chic 的回答,因为我到处都有图标,所以我制作了一个测试平台补丁:
import {TestBed} from '@angular/core/testing';
import {MatIconModule, MatIcon} from '@angular/material/icon';
import {Component, Input} from '@angular/core';
export function PatchTestBedMatIcons() {
const original = TestBed.configureTestingModule;
TestBed.configureTestingModule = (moduleDef) => {
return original(moduleDef)
.overrideModule(MatIconModule, {
remove: {
declarations: [MatIcon],
exports: [MatIcon]
},
add: {
declarations: [MockMatIconComponent],
exports: [MockMatIconComponent]
}
});
};
}
@Component({
selector: 'mat-icon',
template: '<span></span>'
})
class MockMatIconComponent {
@Input() svgIcon: any = null;
@Input() fontSet: any = null;
@Input() fontIcon: any = null;
}
然后在你的组件测试中简单地:
import {PatchTestBedMatIcons} from 'src/app/patchTestBedIcons';
PatchTestBedMatIcons();