How to Unit Test a Custom Angular Pipe

Photo by Steve Johnson on Unsplash

Introduction

Unit testing pipes is very straightforward and this article shows you how to create an Angular project with a custom pipe and ensure that it works as designed.

For this tutorial, I’m using VS Code to create and test the custom pipe.

Background

Before you begin writing a custom pipe, you should first remind yourself that Angular pipes are used to transform input to a required output.

Angular has some out-of-box pipes you can use:

  • CurrencyPipe: for formatting currency codes to three letters (GBP, USD)
  • LowerCasePipe/UpperCasePipe: makes all letters in a string lower/uppercase
  • DatePipe: for handling different conventions of displaying dates
  • PercentPipe: formats a number as a percent string

You can also use pipes to filter input data.

Create the Angular Project

Create a new Angular project from the terminal called demo in a new folder on your drive:

mkdir tutorial-pipes
cd tutorial-pipes
ng new demo

For the questions that come up just select anything — these are unimportant to the tutorial:

Would you like to add Angular routing? (y/N) Y
Which stylesheet format would you like to use? CSS

Then type the following to load the project in VS Code:

code .

If this doesn’t work, go to VS Code and type ⇧ + ⌘ + P and search and click Install ‘code’ command in PATH:

Open the AppComponent file and replace it with the following code:

import { Component } from '@angular/core';@Component({
selector: 'app-root',
template: `{{ name | lowercase }}`
})
export class AppComponent {
name = 'George';
}

In the terminal, run the following to enter the source folder build and serve the application:

cd demo/src
ng serve -o

A browser window will open, showing my name all in lowercase letters:

Now that you have the project set up, you can implement and test a custom pipe.

Create a Custom Pipe

You need to create a class and implement the PipeTransform interface to transform the input.

From your current location (demo folder) create a custom pipe class called Shorten in a new pipes folder in src, which will replace any characters after a point in a piece of text with ellipses.

mkdir app/pipes
ng g p app/pipes/Shorten

This not only creates the pipe class and respective unit test file but also automatically adds the new ShortenPipe pipe to the declarations array of the AppModule class.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ShortenPipe } from './pipes/shorten.pipe';
@NgModule({
declarations: [
AppComponent,
ShortenPipe
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

We will leave the function implementation blank for now, as we need to prepare unit tests first:

import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(text: string, limit: number): string {
return null;
}
}

Prepare Unit Tests

In the same folder, open the unit test file shorten.pipe.spec.ts

import { ShortenPipe } from './shorten.pipe';describe('ShortenPipe', () => {    it('should return text shorter or equal to limit without ellipses', () => {
// arrange
var shortenPipe = new ShortenPipePipe();
var text = 'George';
var limit = 6;
// act
var result = shortenPipe.transform(text, limit)
// assert
expect(result).toBe(text);
});
it('should return text longer than limit with ellipses', () => {
// arrange
var shortenPipe = new ShortenPipePipe();
var text = 'George';
var expected = 'Georg...'
var limit = 5;
// act
var result = shortenPipe.transform(text, limit);
// assert
expect(result).toBe(expected);
});
});

Run the tests using the following command:

ng test 

In the terminal, you’ll see that both tests have failed as expected because you’re just returning null from the custom pipe function:

TOTAL: 2 FAILED, 0 SUCCESS

It’s now time to implement the transform function of your custom pipe and get those tests passed.

Implement the Pipe Transform Function

Write the following code and notice that the tests are now passing:

import { Pipe, PipeTransform } from '@angular/core';@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(text: string, limit: number): string {
if (text.length <= limit) return text;
var truncatedText = text.substring(0, limit);
return truncatedText.concat('...');
}
}

This could all be written using a ternary operator, but I prefer to split things up to clearly convey what the code does to the person reading it.

Additional Considerations for Edge Cases

It’s possible that the text value passed into the pipe transform function could be null or undefined, which would throw an exception since you’re checking the length of the text string. So in these two cases, let’s return an empty string.

Before you add separate tests for null and undefined cases, you could combine all these test cases into a single test as follows to remove repetition (and make it quick and simple to add more test cases in future):

import { ShortenPipe } from './shorten.pipe';describe('ShortenPipe', () => {    var testCases = [
{ text: null, limit: 6, expectedResult: ''},
{ text: undefined, limit: 6, expectedResult: ''},
{ text: 'George', limit: 6, expectedResult: 'George'},
{ text: 'George', limit: 5, expectedResult: 'Georg...'}
];
testCases.forEach(testCase => {
it (`should return ${testCase.expectedResult} for text ${testCase.text} and limit ${testCase.limit}`, () => {
// arrange
var shortenPipe = new ShortenPipe();
// act
var result = shortenPipe
.transform(testCase.text, testCase.limit);
// assert
expect(result).toBe(testCase.expectedResult);
});
});
});

Now update the pipe transform function to handle these edge cases:

import { Pipe, PipeTransform } from '@angular/core';@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(text: string, limit: number): string {
if (!text) return '';
if (text.length <= limit) return text;
var truncatedText = text.substring(0, limit);

return truncatedText.concat('...');
}
}

The limit value will not throw an exception for null, undefined, or negative values (check this for yourself in the console) so it's up to you to decide what should be returned in those cases.

Apply Custom Pipe to HTML

Now update the AppComponent.ts file to apply the custom pipe, specifying a maximum string length of 5 characters.

import { Component } from '@angular/core';@Component({
selector: 'app-root',
template: `{{ name | shorten: 5 }}`
})
export class AppComponent {
name = 'George';
}

This truncates the string and concatenates ellipses as expected:

Thanks for reading! Let me know what you think in the comments section below, and don’t forget to subscribe. 👍

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
George Marklow

George Marklow

George is a software engineer, author, blogger, and abstract artist who believes in helping others to make us happier and healthier.