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

Alisa - Jan 28 '19 - - Dev Community

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player