DEV Community

Cover image for How To Level Up Your Angular Unit Testing Game (3/3)
Alisa
Alisa

Posted on

How To Level Up Your Angular Unit Testing Game (3/3)

This is the third post in the series about Angular unit testing. If you are unfamiliar with using Angular CLI or making changes to Karma configuration, please read the first two posts in the series.

In the last post, we learned how to edit karma.conf.js to add reporters and set coverage thresholds. In this post, we'll create shared testing files and create our own parameter to pass in while testing to limit the files that the test runs. As projects become larger, running all the tests can take a while, so this is an easy way to target tests. It's time to level up your testing game and get pumped up to unit test!

Angular CLI doesn't expose all the configuration options Karma has, so if targeting tests isn't your thing, maybe there's other ways you tailored Angular CLI tests. I'm showing this as an example of how to work around limitations in Angular CLI, not a suggestion on correct ways to configure Karma. I'd love to hear all about the ways you've tailored Angular CLI unit tests in the comments!

Follow Along

We're using Angular CLI to test the application and using code from a previous post I wrote.

You can clone the tutorial-angular-httpclient and run tests too. All the level ups discussed in the posts are found in "test-configuration" branch.

Create Shared Testing Files

As your app grows, you may find that you're creating the same fake more than once. We can DRY things up a bit by creating testing files.

Create your fake and place it in a testing folder. For ease, I named my folder "testing" and made it a child of "src" directory. If you're following the repo, I created a fake-user.service.ts.

In $/src/tsconfig.app.json, add the "testing" folder in the "exclude" array. It needs to match a glob pattern. So in my case I have

"exclude": [
      "test.ts",
      "**/testing/*",
      "**/*.spec.ts"
]

Enter fullscreen mode Exit fullscreen mode

Your spec files can now refer to your shared fake instead of having to define it in the file!

I also like to add a shortcut path to the "testing" folder so that I don't have the world's worst import statement. In your $/tsconfig.json, add a "paths" property after "libs" to add a shortcut to your testing folder like this

"lib": [
      "es2018",
      "dom"
],
"paths": {
      "@testing/*": ["src/testing/*"]
}
Enter fullscreen mode Exit fullscreen mode

In the spec files, I can import my fake users service by using

import { FakeUserService } from '@testing/fake-user.service';
Enter fullscreen mode Exit fullscreen mode

Target Tests Using Jasmine Constructs

Before diving in too deep into custom code to target tests, let's start by using Jasmine's native constructs to force only specific tests to run. You can force tests by prepending 'f' to the test suite or test.

So change describe to fdescribe or it to fit. Then only the tests with the 'f' prefix will run. Your test output still includes all tests (it skips the tests not prefixed).

You'll want to remember to remove the 'f' prefixes. Luckily you can add a lint rule to remember to remove the forced tests.

In your $/tslint.json, add a new rule called "ban". We'll add "fit" and "fdescribe" like this:

"ban": [
      true,
      ["fit"],
      ["fdescribe"]
]

Enter fullscreen mode Exit fullscreen mode

But, using the native construct has its disadvantages. You have to edit the code and remember to remove the 'f' prefix. Also, when you have a lot of tests, it can still take time to complete the test run because it still evaluates whether to run each test. Then your output is full of test cases that were skipped. Boooo... Luckily, we can work around this.

Target Tests By Limiting Files

A way to run only the file you want is to edit the regex Angular CLI uses to find all *.spec files. We can hardcode the test files we want to run.

In $/src/test.ts, find the line

const context = require.context('./', true, /\.spec\.ts$/);
Enter fullscreen mode Exit fullscreen mode

Edit the regex to target the test files you want, such as

const context = require.context('./', true, /\.service\.spec\.ts$/);
Enter fullscreen mode Exit fullscreen mode

to run all the service.spec.ts files.

This method certainly gets the job done. It speeds up the test run and only shows the service files in the console output, but now we have no lint safety to remember to revert changes we made. There's another option-- to create a custom parameter.

Create Custom Parameter

I'd like to pass in the files for the test run. We can add custom code to enable us to do this. Sometimes it's fun to take the covers off a library and dig in to the code ourselves. Taking things apart and putting it back together in way that works better for our needs helps us learn and makes us better engineers and craftsmen.

Warning: Angular v7 purposefully limits parameters passed in to Karma so this solution only works on Angular v6. Follow the instructions with at the risk of it breaking when you upgrade, but maybe it will give you some ideas of the sorts of things you can try!

Handle CommandLine Argument

In the karma.config.js, we need to parse the commandline arguments to grab the test files. I'm going to call this parameter specs.

At the top of karma.config.js define a variable to hold the list of test files and parse through the command line arguments to grab the list of test files. My code looks like this (yep, it's a little ugly-- it can certainly be cleaned up):


let specs = [];
if (process.argv.indexOf('specs') !== -1) {
  specs = process.argv.slice(process.argv.indexOf('specs') + 1);
  specs = specs.slice(0, specs.findIndex(el => el.includes('--')) === -1 ? specs.length : specs.findIndex(el => el.includes('--')));
}
Enter fullscreen mode Exit fullscreen mode

Then add specs in the args array:

client: {
      args: [specs],
      clearContext: false // leave Jasmine Spec Runner output visible in browser
},
Enter fullscreen mode Exit fullscreen mode

That's it for karma.conf.js.

Create Regex Of Files To Test

So now we can pass in a list of files to limit tests to. We do this in the test.ts file.

We need to get a hold of the karma context, so declare it by adding

declare const __karma__: any;
Enter fullscreen mode Exit fullscreen mode

as the first line after the import statements.

Then we need to build out the file list regex. I grabbed the list of files from the karma context and added files to a variable. We still want to run all tests if we don't limit the files.

const args = __karma__.config.args[0];
let fileRegex: RegExp = /.*/;
if ( args && args.length) {
  const files = args.join('|');
  fileRegex = new RegExp(`(${files})\.spec\.ts$`);
}
Enter fullscreen mode Exit fullscreen mode

We want to use fileRegex instead of the one Angular defined, so the line where we hard coded service tests previously changes to

const specFiles = context.keys().filter(path => fileRegex.test(path));
Enter fullscreen mode Exit fullscreen mode

Lastly, we want to load the modules properly to take in to account specFiles

specFiles.map(context);
Enter fullscreen mode Exit fullscreen mode

To see all the code in context, here's a gist of what we just did.

Now we can start using this new parameter.

Here's the kicker. Angular CLI won't recognize the specs parameter, but there's a trick to get Angular CLI to accept it by including the the app name. Create a npm script called test:specs

"test:specs": "ng test tutorial-angular-httpclient specs"
Enter fullscreen mode Exit fullscreen mode

Run the tests by passing the list of files through npm. You can add other commands as you like. For example

npm run test:specs -- user.service auth.interceptor --watch false --browsers ChromeHeadless
Enter fullscreen mode Exit fullscreen mode

Hey hey! Finally only running the test files you want to target!

I'd love to hear how you level up your unit testing!

Top comments (10)

Collapse
 
parthoshuvo profile image
Shuvojit Saha

Hi,

Thanks for sharing the testing series. It works on below Angular 7 particularly for "Create Custom Parameter". I went through your process in my Angular 7 app but it didn't work. Would you suggest how to use "custom command line parameter" testing in Angular 7?

Thanks.

Collapse
 
alisaduncan profile image
Alisa

Thanks Shuvojit!

You are correct, the custom parameter doesn't work for Angular v7. I started working on this while still on Angular v6 and waited too long before posting. I decided to post the custom parameter section anyway since some people may not be able to update their Angular version.

If you are able to figure out a way to submit custom parameters in Angular v7 or v8, please share. I'd love to hear about it.

Collapse
 
engrmubashir profile image
miq

Great articles, really loved the series :). I need your suggestion with my Angular unit test problem. I have a 'abc.service' which initializes a 'Secondary App' for a remote signup for account creation. When I run the tests it throws error 'Secondary App already exists'. But when I comment that initialization method, everything works fine. How can I solve this problem? I tried the following:

  1. Exclude that 'abc.service' in Karma.config (but does not seem to work)
  2. I am using fdescribe with spec files to run specific tests
  3. This seems an app compilation problem during test run (Karma finds two app initializations)

Please help me out, thanks a lot!

Collapse
 
alisaduncan profile image
Alisa

Hi there! I just saw your comment so hopefully this response isn't too late. Are you testing your 'abc.service' and it has a reference to another service? or something else?

Collapse
 
jenc profile image
Jen Chan

Thanks for writing this tutorial. Just got started looking into unit testing and this offered a really concise overview.

Collapse
 
alisaduncan profile image
Alisa

Thank you for your kind words!

Collapse
 
bijenderk82 profile image
Bijender

Thanks @alisa , this series is really helpful. You have covered almost all the important points required to start the testing. Appreciate your time and efforts

Collapse
 
alisaduncan profile image
Alisa

Thank you for your kind words!

Collapse
 
briancodes profile image
Brian • Edited

Really helpful series of articles. Will be adding 'html' to the coverage, and using the @shortcut/imports - some great testing tips here 👍

Collapse
 
alisaduncan profile image
Alisa

Thanks! I'm glad to hear it!