Photo by Guillaume TECHER on Unsplash
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
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;
}
}
And it’s related test:
import { FilterPipe } from "./filter.pipe";
describe("FilterPipe", () => {
it("create an instance", () => {
const pipe = new FilterPipe();
expect(pipe).toBeTruthy();
});
});
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
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');
}
}
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);
}
}
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>
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();
});
});
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();
});
});
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');
});
});
});
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