Is it possible to mock custom Angular 2 Material SVG icons for unit tests?

I was able to use the overrideModule method to stub MdIcon. The documentation is sparse but I was able to find a GitHub issue where Angular team members discuss how to override declarations. The idea is to remove the component from the MdIconModule so that we can declare our own mock icon component.

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ TestedComponent ],
      imports: [
        RouterTestingModule.withRoutes([]),
        SharedModule,
      ],
    })
    .overrideModule(MdIconModule, {
      remove: {
        declarations: [MdIcon],
        exports: [MdIcon]
      },
      add: {
        declarations: [MockMdIconComponent],
        exports: [MockMdIconComponent]
      }
    })
    .compileComponents();
  }));

The MockMdIconComponent is defined very simply

@Component({
  selector: 'md-icon',
  template: '<span></span>'
})
class MockMdIconComponent {
  @Input() svgIcon: any;
  @Input() fontSet: any;
  @Input() fontIcon: any;
}

I used this approach because I am not importing the Material modules individually and I did not want my test to have to make Http calls to get the svg icons. The MockMdIconComponent could be declared in the testing module but I chose to declare/export it in the module override so that I could extract the object into a test helper.


Answering my own question:

After much trial/error with items like mocking the MdIconRegistry or using componentOverride() etc with no luck I no longer use a shared module within my tests.

Instead, I declare the MdIcon component directly in my testing module using a mock version of the class.

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 is simply a blank class that matches the selectors:

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.


This is a late answer. Just in case anyone come across this, there is an alternative other than OP's solution (which is nice as well):

Imports MaterialModule using forRoot()

TestBed.configureTestingModule({
        declarations: [
            TestComponent
        ],
        imports: [SharedModule, MaterialModule.forRoot()],
        providers: [{ provide: Router, useValue: routerStub }]
    });
    TestBed.compileComponents();

Get the injected MdIconRegistry and DomSanitizer

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

Configure them as you did in normal app

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