Test pipe with dependencies on services

13,342

Solution 1

Because of the DI in your pipe, you need to configure a test environment (test bed) to resolve the dependency:

import { BrowserModule, DomSanitizer } from '@angular/platform-browser';
import { inject, TestBed } from '@angular/core/testing';

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

  it('create an instance', inject([DomSanitizer], (domSanitizer: DomSanitizer) => {
    let pipe = new SanitiseHtmlPipe(domSanitizer);
    expect(pipe).toBeTruthy();
  })); 
});

Solution 2

just in case anyone would like to reuse the constructor of the Pipe, you can use the TestBed to acheive the same result :

  let pipe: SafeHtmlPipe;
  let sanitized: DomSanitizer

  beforeEach(async() => {
    TestBed.configureTestingModule({
      providers: [DomSanitizer]
    });
    sanitized = TestBed.get(DomSanitizer);
    pipe = new SafeHtmlPipe(sanitized);
  });

  it('create an instance', () => {
    expect(pipe).toBeTruthy();
  });

Solution 3

In case you want to mock the whole providers and don't wanna use the constructor, this is how I do it (with Jest but replace the spy with your regular jasmine.createSpyObj)

spec

describe("MyPipe", () => {
  let pipe: MyPipe;
  const myServiceSpy = { myFunction: jest.fn() };

  beforeEach(() => {
    jest.clearAllMocks();
    TestBed.configureTestingModule({
      providers: [
        MyPipe,
        {
          provide: MyService,
          useValue: myServiceSpy
        }
      ]
    });

    pipe = TestBed.inject(myPipe);
  });

  it("create an instance", () => {
    expect(pipe).toBeTruthy();
  });
});

pipe

@Pipe({
  name: "myPipe"
})
export class MyPipe implements PipeTransform {
  constructor(private readonly myService: MyService) {}

  transform(value: Item): boolean {
    // stuff with your service
    return true;
  }
}
Share:
13,342
Ben Taliadoros
Author by

Ben Taliadoros

I am a Software Engineer working for a cyber security company in London. Answer my questions and I will send you sweets.

Updated on June 06, 2022

Comments

  • Ben Taliadoros
    Ben Taliadoros about 2 years

    I have a pipe that sanatises HTML as below:

    import { Pipe, PipeTransform } from '@angular/core';
    import { DomSanitizer } from '@angular/platform-browser';
    
    @Pipe({
        name: 'sanitiseHtml'
    })
    
    export class SanitiseHtmlPipe implements PipeTransform {
    
    constructor(private _sanitizer: DomSanitizer) {}
    
        transform(value: any): any {
          return this._sanitizer.bypassSecurityTrustHtml(value);
        }
    
    }
    

    I want to test it as below:

    describe('Pipe: Sanatiser', () => {
        let pipe: SanitiseHtmlPipe;
    
        beforeEach(() => {
            pipe = new SanitiseHtmlPipe(new DomSanitizer());
        });
    
        it('create an instance', () => {
            expect(pipe).toBeTruthy();
        }); 
    });
    

    The DomSanatizer is an abstract class which is autowired by typescript by passing it into a constructor:

    constructor(private _sanitizer: DomSanitizer) {}
    

    Currently I get the typescript errror:

    Cannot create an instance of the abstract class 'DomSanitizer'.

    Does anyone know what typescript does when instantiating dependencies passed into a constructor in Angular? Or what the way to test something like this is?

  • Ben Taliadoros
    Ben Taliadoros over 6 years
    I actually got the test running by commenting out the line: TestBed.resetTestEnvironment(); Remove this and I'll mark correct
  • Csaba Toth
    Csaba Toth almost 6 years
    I also needed to add import { BrowserModule, DomSanitizer } from '@angular/platform-browser';
  • codeepic
    codeepic over 5 years
    @Jota.Toledo What's the difference between new SanitiseHtmlPipe(new DomSanitizer()) and injecting it like you did above. I am testing the pipe that has a custom service injected into it and I mocked my service and injected it into pipe like this: const pipe = new DataHealthPipe(new DateServiceMockBefore17thApril()); It is working for me, but I wonder whether this is the best approach. I had to mock the service 3 times, so each time its getFullDate() method returns different date, for each test case.
  • Jota.Toledo
    Jota.Toledo over 5 years
    @codeepic "What's the difference between new SanitiseHtmlPipe(new DomSanitizer()) and injecting it like you did above" its not possible to use new as the class is abstract
  • Jota.Toledo
    Jota.Toledo over 5 years
    Depending on the kind of dependencies that the SUT has, one can choose between real or mock objects. In the original OP case, mocking the DomSanitizer feels like an overkill, as the implementation is really straightforward. In your case, I cant tell if your service is complex enough to be worth mocking or not...
  • codeepic
    codeepic over 5 years
    @Jota.Toledo - ok, you can't new up abstract classes, makes sense to use inject. In my case the getFullDate() method in mocked service relies on member variables of the class, some of them Observables that are set by user. I don't want to pull the whole gorilla and a tree if I want a banana - I only need the return object from the method, so mocking the DateService class and returning different values from DateService.getFullDate() 3 times for each test case seemed like lesser evil, even when I have to mock the class and its method 3 times.
  • Jota.Toledo
    Jota.Toledo over 5 years
    @codeepic doesnt sound that complex. I dont know exactly what you mean by mock the class and its method 3 times, but my approach would be to provide a mock object and then spy with jasmine on the getFullDate() method and return what you need for your tests. Feel free to open a new question and tag me on it
  • codeepic
    codeepic over 5 years
    @Jota.Toledo Thanks for the tip with the spy and returnValue - I wasn't aware of that - I need to check Jasmine docs to see on what other goodies I am missing. I refactored my unit tests.
  • martin-g
    martin-g over 4 years
    but it seems sanitized is not usable: this.sanitizer.bypassSecurityTrustHtml is not a function. This is the error when I try to use the pipe.
  • Alain Boudard
    Alain Boudard over 4 years
    Well, as far as I can tell, it's not a matter of method, this is because for whatever reason, this function and others are static, and therefore, you can't call them like that in your test. If I'm not wrong, you need to mock them like this if you want to test the methods calls : <pre><code>TestBed.configureTestingModule({ imports: [BrowserModule], providers: [ {provide: DomSanitizer, useValue: { sanitize: () => 'safeString', bypassSecurityTrustHtml: () => 'safeString' } } ] });</code></pre>
  • Alain Boudard
    Alain Boudard over 4 years
    sorry, abstract methods, not static
  • martin-g
    martin-g over 4 years
    Is there a way to inject the DomSanitizerImpl that is actually used in non-test code ?