How do I go about testing a Pipe which depends on DomSanitizer?
Asked Answered
N

2

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.

Nez answered 27/7, 2019 at 22:4 Comment(0)
I
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.

Incipient answered 28/7, 2019 at 9:57 Comment(5)
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?Nez
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?Incipient
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.Nez
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 senseIncipient
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!Nez
C
14

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();
  });
});
Cierracig answered 9/2, 2020 at 4:10 Comment(1)
const domSanitizer = TestBed.inject(DomSanitizer); from now onPaletot
I
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.

Incipient answered 28/7, 2019 at 9:57 Comment(5)
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?Nez
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?Incipient
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.Nez
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 senseIncipient
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!Nez

© 2022 - 2024 — McMap. All rights reserved.