Unit Testing Angular Pipes
While unit tests in general can get complex really fast (especially when it is required to mock a lot of dependencies), unit testing Angular Pipes is fortunately one of the easier exercises. Likewise, they usually have zero to few dependencies and implement a single interface function that can be treated as a blackbox. The transform interface of Angular Pipes has clearly defined inputs and outputs and usually no side effects, which makes it a perfect entry in unit testing in general.
I have prepared an example repository on my GitHub that contains different testing scenarios in an Angular context. As usual, it is using jest as the unit test runner. The project is derived from my Angular boilerplate I wrote about in an earlier post, which you can use as a template for your future projects.
The pipe under test
I wrote the following, exemplary pipe to automatically generate the href attribute for mailto links on tags. The pipe is pure, which means it only executes the transform function when the object reference of the input changes. This is true for all primitive values like booleans, numbers or strings, which we will use here.
You can find more information on pure and impure pipes on the official Angular documentation page.
Let’s have a look at the source code of the pipe:
I am aware that this pipe is not perfect because this post concentrates on testing the pipe. So please do not judge the pipe itself since it only serves testing purposes.
What should this pipe do? It should:
- Encode the email address
- Support multiple recipients
- Optionally add the “mailto:” prefix
- Optionally add an subject to the email
You can find more information about the structure of mailto links here.
Setup the test file
Since testing Angular Pipes in general is relatively easy for the reasons mentioned above, I want to take the chance to dive into the structure of a test file in jest. Test files usually have a name like “<name-of-your-class>.spec.ts” by convention and the Angular CLI also generates them like that automatically. If your class is called EmailHrefPipe, your TypeScript file will have the name “email-href.pipe.ts” and your test file will consequently be “email-href.pipe.spec.ts”. This also has the reason that Jest needs a way to identify your tests and this is typically achieved via the naming convention.
You can split the file into several parts:
- At the beginning the pipe under test is imported
- describe() starts the test block of this file. It takes a descriptive name as a first argument and an anonymous function as second. You can also nest describe blocks, e.g. for improved structure in your tests or to scope test-related variables.
- beforeEach() is a life cycle hook of Jest that we use to setup the test environment. In this case we only create and instance of the pipe class here, but this hook can quickly be more extensive for Angular Components or Services where you want to setup dependencies and mock data.
- it() starts a single test block. It takes a string that describes the test as the first argument and an a anonymous function that includes the test itself as second.
There are more life cycle hooks in Jest, but beforeEach() is the one that will be used in almost every test scenario. Other hooks are for example beforeAll, afterAll and afterEach. I think the names are kind of self-describing.
The test in the example does not test any logic at all, but I suggest you keep these generated tests to make sure your test setup is working. Even this simple test can fail often enough if you miss to include some dependencies in your test setup.
Testing the Pipe
Further tests should test the functionality of the pipe of course. Fortunately, the pipe only has a single interface function that needs to be tested. It has a defined input and output so that we can treat it as a black-box in our test.
I have decided to implement two tests for this pipe: One test for single string inputs and one test for an array of strings. Of course you can also add all of the tests into a single it() block, but I like to structure it that way. It is your decision whether to handle it the same.
In the test I have created a variable for the input I want to use for my tests: The email address “hans.meier@supercompany.com”. After that I tell Jest what behavior I expect the output from the pipe to be. It comes in very handy here the syntax can be read kind of natural language here: I “expect” the output from the function “to equal” some value. The “toEqual()” part is called a matcher function in Jest and there already exist a lot of them by default. You can find all of them on the official Jest documentation about expect().
Of course you can extend Jest by adding custom matchers, but this is not covered in this article. There are also libraries like the testing-library packages or jest-marbles which I can really recommend and I will use in later articles.
As you can see in the example, I have covered various input alternatives for the transform function: One and multiple recipients, with and without prefix and also the optional subject.
Often you will find yourself testing different scenarios and alternative input values which gives you even more confidence that your code is working. At the same time it is important to also test different code paths inside your functions: Do you have any if-statements or error handling inside? Please also check if these code paths are working as expected. In a previous article I said that you will never have 100% test coverage, but at least for large parts of your TypeScript code you can aim for it with an acceptable effort.
Summary
In this article you learned how to:
- Write a mailto link in HTML
- Setup a basic test file in Jest and Angular
- Write simple unit tests for an Angular Pipe with clearly defined inputs and outputs
- Utilize Jest’s default matcher “expect(a).toEqual(b)”
- Unit test Angular Pipes ;-)
An the future you will learn how to test more complex Angular-specific classes (e.g. Services, Components and Directives). Feel free to tell me if you have a specific problem related to unit tests in Angular you want me to write about.