Praneet Loke
Posted on June 12, 2020
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>
...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.
});
}
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);
});
}
That's it! Happy testing!π
Posted on June 12, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 18, 2024