DEV Community

Cover image for Unit Testing Angular Components That Use MatIconRegistry
Praneet Loke
Praneet Loke

Posted on

Unit Testing Angular Components That Use MatIconRegistry

If you use an Angular Material component (part of Angular Components) that relies on the MatIconRegistry, chances are you have wondered how you could test it with mock data. I recently ran into this, and figured I would turn it into a post as a way to document it for myself. Who knows -- maybe one of you out there might find this helpful as well!

MatIconRegistry is useful if you have a custom SVG icon set that you want to use within your app. This allows you to explicitly add the icons under a custom namespace using the addSvgIcon(iconName: string, url: string) method.

Let's say you use the mat-icon component in your component's template:

<mat-icon svgIcon="icon-name"></mat-icon>
Enter fullscreen mode Exit fullscreen mode

...testing your component means you'll need to ensure that the the icon is served properly, at least with a fake icon.

To do that, you'll need to first add the icon names that your component expects to find in the registry. Ideally, you'd do this in the beforeEach setup function.

import { HttpClientTestingModule } from "@angular/common/http/testing";
import { inject, TestBed } from "@angular/core/testing";
import { MatIconRegistry } from "@angular/material/icon";
import { DomSanitizer } from "@angular/platform-browser";

describe("TestingComponent", () => {
    const fakeSvg = `<svg><path id="someId" name="someSvg"></path></svg>`;
    const fakeIconPath = "/fake/icon.svg";

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
    }).compileComponents();
  });

  beforeEach(inject([MatIconRegistry, DomSanitizer], (mir: MatIconRegistry, sanitizer: DomSanitizer) => {
    // The `MatIconRegistry` will make GET requests to fetch any SVG icons that are in the registry. More on this below...
    const sanitizedUrl = sanitizer.bypassSecurityTrustResourceUrl(fakeIconPath);
    // Make sure that the icon name matches the icon name your component would be looking up.
    mir.addSvgIcon("icon-name", sanitizedUrl);
  }));

  it("test my component", () => {
     // Create a test fixture of your component's instance and test your component.
  });
}
Enter fullscreen mode Exit fullscreen mode

That should do it, right? Nope. Your test would likely fail with a timeout error. But the reason may not be immediately clear. The answer lies within the functionality of the MatIconRegistry and what happens when you use the attribute with svgIcon with a <mat-icon> component.

Looking at the docs for <mat-icon>, note how it says this:

In order to guard against XSS vulnerabilities, any SVG URLs and HTML strings passed to the MatIconRegistry must be marked as trusted by using Angular's DomSanitizer service.

MatIconRegistry fetches all remote SVG icons via Angular's HttpClient service. If you haven't included HttpClientModule from the @angular/common/http package in your NgModule imports, you will get an error at runtime.

That's good. We did that in our beforeEach call above. Note that for unit testing we use HttpClientTestingModule and not HttpClientModule because, well, it's a unit testing compatible HTTP client. One that doesn't actually hit a real endpoint. This means we need to control the response we send back to callers making HTTP requests based on the URL they request. Sort of like setting up a method spy and returning a mock value when the method is called. Only here we would be matching against the URL being requested and return an appropriate response.

So let's update our snippet above:

import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing";
import { inject, TestBed } from "@angular/core/testing";
import { MatIconRegistry } from "@angular/material/icon";
import { DomSanitizer } from "@angular/platform-browser";

describe("TestingComponent", () => {
    const fakeSvg = `<svg><path id="someId" name="someSvg"></path></svg>`;
    const fakeIconPath = "/fake/icon.svg";

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
    }).compileComponents();
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  beforeEach(inject([MatIconRegistry, DomSanitizer], (mir: MatIconRegistry, sanitizer: DomSanitizer) => {
    // The `MatIconRegistry` will make GET requests to fetch any SVG icons that are in the registry. More on this below...
    const sanitizedUrl = sanitizer.bypassSecurityTrustResourceUrl(fakeIconPath);
    // Make sure that the icon name matches the icon name your component would be looking up.
    mir.addSvgIcon("icon-name", sanitizedUrl);
  }));

  it("test my component", () => {
     // Create a test fixture of your component's instance and test your component.
     ...
     ...
     // Use this to capture icon requests
     // and flush it manually so that the source observable will emit a value. Otherwise, async calls will timeout waiting for a response.
     httpTestingController.match(fakeIconPath)[0].flush(fakeSvg);
  });
}
Enter fullscreen mode Exit fullscreen mode

That's it! Happy testing!🙂

Discussion (0)