DEV Community

loading...
Cover image for Test Angular Pipes With Services

Test Angular Pipes With Services

David Dal Busco
Creator of DeckDeckGo | Organizer of the Ionic Zürich Meetup
Originally published at Medium Updated on ・4 min read

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-five days left until hopefully better days.


Today I spent much time deep focused at writing new Angular components and their related unit tests, that I even missed this morning online “stand-up” and almost feel like I spend my day in some kind of vortex.

Anyway, I like this challenge, I don’t want to skip today’s blog post and I would like to share with you how I tested a new pipe I created. Moreover, I don’t pretend to be the champion of the exercise, therefore, if you notice anything which can be enhanced, ping me with your comments, I would be happy to improve my skills 🙏.


Create A Pipe

Let’s first create a blank pipe called “filter” with the ng command line.

ng g pipe filter
Enter fullscreen mode Exit fullscreen mode

This creates a blank pipe like the following:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {

  transform(value: any, ...args: any[]): any {
    return null;
  }

}
Enter fullscreen mode Exit fullscreen mode

And it’s related test:

import { FilterPipe } from './filter.pipe';

describe('FilterPipe', () => {
  it('create an instance', () => {
    const pipe = new FilterPipe();
    expect(pipe).toBeTruthy();
  });
});
Enter fullscreen mode Exit fullscreen mode

You can be or not an Angular fan but I think we can all be agree that it’s pretty cool to have a CLI which creates class and related test without any effort.


Create A Service

As staten in my opening, the goal is to test a pipe which uses an injected service.

ng g service translation
Enter fullscreen mode Exit fullscreen mode

For demo purpose, we create this dummy service “translation” wich return not that much except either “Génial” or “Awesome” as an observable.

import { Injectable } from '@angular/core';

import { Observable, of } from 'rxjs';

@Injectable({
    providedIn: 'root'
})
export class TranslationService {
    translate(lang: string): Observable<string> {
        return of(lang === 'fr' ? 'Génial' : 'Awesome');
    }
}
Enter fullscreen mode Exit fullscreen mode

Implement Pipe

Our service being ready, we use it to enhance our pipe.

import { Pipe, PipeTransform } from '@angular/core';

import { TranslationService } from './translation.service';
import { Observable } from 'rxjs';

@Pipe({
    name: 'filter'
})
export class FilterPipe implements PipeTransform {
    constructor(private translationService: TranslationService) {}

    transform(lang: string): Observable<string> {
        return this.translationService.translate(lang);
    }
}
Enter fullscreen mode Exit fullscreen mode

Which by the way can be use with the help of the async pipe in a template (in following example, lang is a public string variable of the component)

<textarea [value]="lang | filter | async"></textarea>
Enter fullscreen mode Exit fullscreen mode

Update Pipe Test

Locally I’m still able to run my test without errors but, because we are now injecting a service in our pipe, if we open the related unit test we notice a TypeScript error on the constructor TS2554: expected 1 arguments, but got 0 . To fix this we have now to either inject the service or mock it.


Resolving Service In Test

You can either resolve the service via the inject function or TestBed . Because the first solution didn’t worked out for me, the second one was my fallback.

import { FilterPipe } from './filter.pipe';
import { TestBed } from '@angular/core/testing';
import { TranslationService } from './translation.service';

describe('FilterPipe', () => {
  beforeEach(() => {
    TestBed
      .configureTestingModule({
        providers: [
          TranslationService
        ]
      });
  });

  it('create an instance', () => {
    const service: TranslationService =
                              TestBed.get(TranslationService);

    const pipe = new FilterPipe(service);
    expect(pipe).toBeTruthy();
  });
});
Enter fullscreen mode Exit fullscreen mode

Mock Service

Another solution, the one I actually finally applied, is creating a mock of the service instead of providing it.

import { FilterPipe } from './filter.pipe';
import { of } from 'rxjs';
import { TranslationService } from './translation.service';

describe('FilterPipe', () => {
  let translationServiceMock: TranslationService;

  beforeEach(() => {
    translationServiceMock = {
      translate: jest.fn((lang: string) => of('Awesome'))
    } as any;
  });

  it('create an instance', () => {
    const pipe = new FilterPipe(translationServiceMock);
    expect(pipe).toBeTruthy();
  });
});
Enter fullscreen mode Exit fullscreen mode

Test Pipe Transform

So far we were able to test that our pipe can be created even if it relies on a service but we are still not effectively testing its outcome. Therefore, here is the final piece, in which I use the mock of the service. Basically, once the pipe is created, we can access its transform method and proceed with some common testing.

import { FilterPipe } from './filter.pipe';
import { of } from 'rxjs';
import { take } from 'rxjs/operators';
import { TranslationService } from './translation.service';

describe('FilterPipe', () => {
  let translationServiceMock: TranslationService;

  beforeEach(() => {
    translationServiceMock = {
      translate: jest.fn((lang: string) => of('Awesome'))
    } as any;
  });

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

  it('should translate', () => {
    const pipe = new FilterPipe(translationServiceMock);
    pipe.transform('en')
      .pipe(take(1))
      .subscribe((text: string) => {
        expect(text).not.toBe(null);
        expect(text).toEqual('Awesome');
      });
  });
});
Enter fullscreen mode Exit fullscreen mode

Summary

It still takes me a bit to find the right testing setting for projects, specially when they are new, but as soon as everything is in place as soon as I can access the nativeElement to perform queries, as I would do in a Web Component, I feel more comfortable and it began to be fun 😁.

Stay home, stay safe!

David

Cover photo by Guillaume TECHER on Unsplash

Discussion (4)

Collapse
sebastiandg7 profile image
Sebastián Duque G • Edited

Thanks a lot for your post! Many times pipes are considered as "just pipes", and are forgot during testing.

Collapse
daviddalbusco profile image
David Dal Busco Author

Really happy to hear I was not that wrong and that this small article might help someone else too 😃

Thx for the nice feedback, really cool!

Collapse
testree profile image
Roberto Romello

This is awesome - thank you!

Thread Thread
daviddalbusco profile image
David Dal Busco Author

First comment I receive today, what a way to begin a quarantine day 😄

Thank you Roberto!