6

Angular version: 8.1.2
Testing tools: Karma and Jasmine, as pre-installed by ng new

I am currently working on my first ever Angular project. As a part of this, I have created a pipe which calls DomSanitizer.bypassSecurityTrustResourceUrl. I do this in order to be able to use them in iframes. I now want to implement tests for this pipe. Here is the code for it:

import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";

@Pipe({
  name: 'safe'
})
export class SafeResourceUrlPipe implements PipeTransform {

  constructor(private sanitizer: DomSanitizer) { }

  transform(url: string): SafeResourceUrl | string {
    return this.sanitizer.bypassSecurityTrustResourceUrl(url);
  }

}

The auto-generated spec file only looked like this:

import { TestBed, async } from '@angular/core/testing';
import { SafeResourceUrlPipe } from './safe-resource-url.pipe';
import { DomSanitizer } from '@angular/platform-browser';

describe('Pipe: SafeResourceUrle', () => {
  it('should create an instance', () => {
    let pipe = new SafeResourceUrlPipe();
    expect(pipe).toBeTruthy();
  });
});

That this wouldn't work VSCode told me before I even ran the tests, because SafeResourceUrlPipe's constructor expects an argument. So far so good, but I don't know what to do now. I can't just use new DomSanitizer, because it is an abstract class.

What I have tried is creating a mock class that implements DomSanitizer, but with that I can't do much more than testing whether the pipe is even created, and I knew that before already. What I would like to test is whether it properly does its job transforming inputs, but I can hardly test that when I'm pseudo-implementing the main dependency.

I have done some Googling about this and I suspect it will turn out to be something obvious, but I couldn't find it.

2 Answers2

13

You don't need to mock DomSanitizer, it becomes available when you import BrowserModule. So you only need to import the module when configuring the test module and retrieve it with TestBed.get() method to pass it to your pipe constructor.

import { BrowserModule, DomSanitizer } from '@angular/platform-browser';

describe('Pipe: SafeResourceUrl', () => {

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

  it('should create an instance', () => {
    const domSanitizer = TestBed.get(DomSanitizer);
    const pipe = new SafeResourceUrlPipe(domSanitizer);
    expect(pipe).toBeTruthy();
  });
});
j3ff
  • 5,719
  • 8
  • 38
  • 51
1

I'd recommend using the Angular Testbed to inject a mock of the dom sanitizer like this.

beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [SafeResourceUrlPipe],
      providers: [
           SafeResourceUrlPipe,
          { provide: DomSanitizer, useValue: {bypassSecurityTrustResourceUrl(){}}
     ]
    });
  }));

Then

describe('Pipe: SafeResourceUrle', () => {
  it('should create an instance', () => {
    let pipe = TestBed.get(SafeResourceUrlPipe);
    expect(pipe).toBeTruthy();
  });
});

p.s. the useValue is important here. If you only provide a value here then its fine. If you want to replace that with a full mocked class you must useClass (small slip up that most people get stuck on)

export class MockDomSanitizer {
    bypassSecurityTrustResourceUrl() {}
    otherMethods(){}
}

This should allow you to run the pipe with the mocked out dom sanitizer method.

James
  • 2,516
  • 2
  • 19
  • 31
  • Thanks for the answer, though I think you missed something. At first, when trying your tip, I got a `NullInjectorError: No provider for SafeResourceUrlPipe`, but adding `SafeResourceUrl` in `providers` fixed it. I do however have another question: can I actually test my pipe's `transform` method with this, and if not, how could I achieve that? – Rummskartoffel Jul 28 '19 at 10:44
  • oops, yes I think it may have to go in providers (too). Well that was another thing I was wondering. Why do you need to test it? In unit testing practices you'd really not need to test this as you're testing Angular code rather than your own? – James Jul 28 '19 at 10:58
  • Yeah, I noticed that as well earlier, but since I am a beginner, I thought it might help to know anyway. If I have a situation in the future where my own code is complex enough to warrant testing of (in this case) the `transform` method, then I would need to use (in this case) DomSanitizer with its methods actually available to me. I could then of course try mocking the class, but since it is abstract, I then wouldn't know how it actually works 'under the hood'. I thought it might be better to solve that problem for this much simpler case, although for this specifically it doesn't matter. – Rummskartoffel Jul 28 '19 at 11:07
  • Yeah I think that's a fair enough thought process. However, you shouldn't *need* to care about internal implementations when mocking. That's the point of it. You just tell an external function to return whatever you want (you only need to know the return type) Then you can test your function. e.g. ... I want my function to return true when this external function returns 'Hello World'. We shouldn't care how it does that, all we care is that *your* function behaves like you expect. Hope I am making some sense – James Jul 28 '19 at 11:29
  • you're probably right. DomSanitizer in particular wouldn't really work with that, because the types its methods return are really weird, but then again, I only just noticed that you'd probably never touch then after they've been created and just insert them into HTML directly. Thanks for your patience! – Rummskartoffel Jul 28 '19 at 12:04