是否可以模拟自定义Angular 2 Material SVG图标进行单元测试?

时间:2017-01-10 18:11:31

标签: unit-testing angular angular-material2

在我应用的根组件中,我正在为md-icon定义自定义SVG图标。当单元测试显示自定义图标的组件时,我收到错误。似乎错误可能是由于我的子组件测试中没有使用/初始化我的根组件。

在设置测试模块时,有没有办法模拟或添加这些自定义图标(或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

完整错误:enter image description here

从模板中删除自定义图标可以解决错误。

我的模板使用如下自定义图标:

<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

4 个答案:

答案 0 :(得分:1)

回答我自己的问题:

经过多次试用/错误,例如嘲笑MdIconRegistry或使用componentOverride()等没有运气的项目,我不再在我的测试中使用共享模块。

相反,我使用模拟版本的类直接在我的测试模块中声明了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只是一个与选择器匹配的空白类:

import { Component } from '@angular/core';

@Component({
    template: '',
    // tslint:disable-next-line
    selector: 'md-icon, mat-icon',
})
// tslint:disable-next-line
export class MdIconMock {
}
  

注意:由于TSLint规则指定了类名/选择器的前缀/格式,我需要为此模拟禁用TSLint。

答案 1 :(得分:1)

这是一个迟到的答案。万一有人遇到这个问题,除了OP的解决方案之外还有其他选择(这也很好):

使用forRoot()

导入MaterialModule
let iconRegistry = TestBed.get(MdIconRegistry);
let sanitizer = TestBed.get(DomSanitizer);

获取注入的MdIconRegistry和DomSanitizer

iconRegistry.addSvgIcon( 'some-icon',
sanitizer.bypassSecurityTrustResourceUrl('assets/img/some-icon.svg'));

像在普通应用中一样配置

public class ShippingCharge {
    public decimal basePrice { get; set; }
    public int baseCount { get; set; }
    public decimal unitPrice { get; set; }
}

答案 2 :(得分:1)

我能够使用overrideModule方法来存根MdIcon。文档很稀疏,但我能够找到一个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可以在测试模块中声明,但我选择在模块覆盖中声明/导出它,以便我可以将对象提取到测试助手中。

答案 3 :(得分:0)

基于@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();