Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

In my app's root component, I am defining custom SVG icons for md-icon. When unit testing a component that displays the custom icon I get an error. It seems that the error is likely due to the fact that my root component is not being used/initialized in my child unit test.

Is there a way to mock or add these custom icons (or md-icon) when setting up the test module? I would simply define the icons in the component I am testing, but I know other components will need them also.

The error:

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

Full error: enter image description here

Removing the custom icons from the template solves the error.


My template is using the custom icons like this:

<md-icon svgIcon="vip">vip</md-icon>

And the root component initializes the icons like this:

this.iconRegistry.addSvgIcon(
    'vip',
    this.sanitizer.bypassSecurityTrustResourceUrl('assets/icons/vip.svg') as string,
);

I set up the test component like this:

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();
}));

Versions

  • Angular 2.3.0
  • Material 2.0.0-beta.1
like image 204
Benjamin Avatar asked Jan 10 '17 18:01

Benjamin


People also ask

What is mocking in unit testing Angular?

Introduction. Mocking is a great idea for testing Angular apps because it makes maintenance easier and helps reduce future bugs. There are a few complex tools, such as XUnit, for mocking an Angular CLI project. You can execute the mocking methods described in this guide only if you use vanilla Jasmine + Angular Testbed ...

How do you mock an Angular component?

A mock component in Angular tests can be created by MockComponent function. The mock component respects the interface of its original component, but all its methods are dummies. To create a mock component, simply pass its class into MockComponent function.


2 Answers

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.

like image 189
Chic Avatar answered Oct 12 '22 10:10

Chic


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.

like image 45
Benjamin Avatar answered Oct 12 '22 10:10

Benjamin