Lesson Monday

To get Jasmine working with our code we'll set up a test runner called Karma. Remember that we can use Jasmine to write our tests but we need another package to actually run the tests. We could conduct our tests using Node in the command line (as we saw at the end of the last lesson), but there's still a problem: Jasmine doesn't recognize ES6 syntax. In other words, it won't work with the import and export statements we're using with webpack! We'll also be using a lot more ES6 syntax soon; for that reason, we need to be able to use Jasmine with ES6 features.

Introduction to Karma

Karma can solve this problem for us. Karma is a popular test-runner that can be used with many different testing frameworks, including Jasmine, Mocha, and other testing libraries. Karma provides several key benefits as a test-runner over Jasmine’s SpecRunner.html file:

  1. It helps us use newer ES6 (and beyond) features of JavaScript with Jasmine.

  2. It allows us to run tests continuously. Each time we save our code our tests will update, which allows us to quickly pinpoint bugs.

  3. Karma also executes our application’s source code in the browser and then runs it against our test code. We can use this functionality to check if our code is compatible with different browsers. For instance, what works in Chrome might not work in Internet Explorer. We won’t cover this feature in depth, but at the very least you should know about it, as testing against different browsers is very important for larger commercial projects. Browser compatibility is a huge pain point in the world of JavaScript development.

  4. We can even use plugins to extend the functionality of Karma. This allows us to create a highly-customized testing environment that has all the tools we need.

While Karma itself is easy to use, configuration can be a bit tricky, especially if we have a unique use case for our applications. Node modules are constantly updating and evolving, and there are often multiple modules that have the same functionality. Some work well, others don’t, and documentation can be spotty.

Installing Karma

That in mind, let’s set up Karma. Take the time to input each of these commands the first time you go through this lesson. You can reuse your completed package.json file for future projects. (Don’t forget to run $ npm install.)

First let’s install Karma itself.

$ npm install [email protected] --save-dev

Next we’ll add packages that help Karma and Jasmine work together. (Note: it’s assumed you’ve already installed Jasmine from the last lesson. If you haven’t, you can do so now.)

$ npm install [email protected] --save-dev

We also need to specify which browser(s) we want Karma to launch in. We’ll install the Chrome launcher. In a real-world environment you’d likely want to test other browsers, too.

$ npm install [email protected] --save-dev

You’ll also need to add karma-cli to your package.json:

$ npm install [email protected] --save-dev

In addition, we need to install a package so that Karma can work with webpack:

$ npm install [email protected] --save-dev

Karma doesn’t understand jQuery on its own, so we need a plugin for that, too:

$ npm install [email protected] --save-dev

Last but not least, we’ll want to make our testing report easy on the eye:

$ npm install [email protected] --save-dev

That’s a lot of Node packages! Now it’s time to initialize Karma by running the following in the root directory of your project:

$ karma init

You’ll be prompted to answer a series of questions. Go ahead and hit Enter until the prompts are finished. If you receive a bash error after running this command, this could be because your machine doesn't recognize the karma command. Double-check that you've successfully installed Karma CLI globally with $ npm install -g karma-cli, as mentioned above.

Configuring Karma

While we could input our source and test files in the command line (as well as other configuration options), we can just as easily configure these options inside the newly-generated karma.conf.js file inside our project. Let’s take care of that, along with all other necessary configuration:

karma.conf.js
const webpackConfig = require('./webpack.config.js');

module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jquery-3.2.1', 'jasmine'],
    files: [
      'src/*.js',
      'spec/*spec.js'
    ],
    webpack: webpackConfig,
    exclude: [
    ],
    preprocessors: {
      'src/*.js': ['webpack'],
      'spec/*spec.js': ['webpack']
    },
    plugins: [
      'karma-jquery',
      'karma-webpack',
      'karma-jasmine',
      'karma-chrome-launcher',
      'karma-jasmine-html-reporter'
    ],
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['Chrome'],
    singleRun: false,
    concurrency: Infinity
  })
}

(If you wish, you can copy and paste this configuration directly into your karma.conf.js file.)

Let’s focus on the options that have been customized and updated:

  • frameworks specifies the frameworks that Karma should recognize and use. We want Karma to understand both jQuery and Jasmine.

  • The files array lets Karma know which files to load. Specifically, we need to load our JavaScript business logic as well as our tests. We use a globbing pattern (the * symbol) to specify that all files ending in .js and all files ending in -spec.js should be loaded.

  • We’ve also added a plugins array. Technically, Karma autoloads all “sibling” modules beginning with karma, so we don’t actually need this array to make our code work. However, there are benefits to explicitly stating the plugins being used (including enhanced readability). It's included here to emphasize that the included functionality we’re about to discuss comes from plugins.

A Karma configuration can also include plugins for preprocessors, frameworks, reporters, and browsers.

  • karma-jquery and karma-jasmine are plugins for the frameworks we’re using inside Karma. For instance, Jasmine is a testing framework while jQuery is a feature library that enhances the functionality of JavaScript.

  • A preprocessor processes files before the browser serves them. In this case, we're running our files through webpack; otherwise they won’t work! We also need to require our webpack configuration so that our Karma file knows how to use it.

  • karma-chrome-launcher is a plugin that we need to specify in browsers. We could test other browsers by adding their launchers to package.json and then specifying them here.

  • karma-jasmine-html-reporter is an example of a reporter. Reporters give us additional information about our tests. This reporter will give us a report in the browser that’s easy to read.

If you choose to customize Karma further, you’ll most likely do so with plugins. You may want to do some research on your own to see what other tools you can add. If you’d like to practice on your own, try adding Istanbul.js, a library that helps you track how well you’ve tested your code.

We’re almost ready to run our tests through Karma! There’s one last thing to do. $ npm test is still pointing at Jasmine. We need to update package.json one more time to make our test command point to Karma. To use Karma to run our tests we need to tell npm exactly where the Karma CLI commands are held. Our installation path for karma-cli is ./nodemodules/karma/bin/karma. The command for running tests is start. We also need to dictate which file to use for our Karma configuration. So, the complete command for running our tests is `> ./nodemodules/karma/bin/karma start karma.conf.js. Let's set ourtest` command to this:

package.json
...
  "scripts": {
    "test": "./node_modules/karma/bin/karma start karma.conf.js"
  },
...

Now, when we run $ npm test, Karma will launch a Chrome browser. However, we haven't written any tests yet. (We'll do that in the next lesson.)

Source Mapping

Recall that we had to add a package (webpack-dev-server) in order to see the correct stack trace for errors. We had to modify our webpack configuration to tell webpack to use the eval-source-maps option for generating a stack trace. When Karma runs our code, it runs webpack as well, but it does not run the source mapping tool. If we want to see correct stack traces while using Karma, we need to install another package called karma-sourcemap-loader. We'll add this package like we have added every other package, and we'll be sure to save it to the list of development dependencies and not to the list of production dependencies, since we don't need it during production, we only need it during testing.

$ npm install [email protected] --save-dev

After installing this package, we need to tell karma to how to use it. In the karma.conf.js file, update the entry for preprocessors:

karma.conf.js
    ...
    preprocessors: {
      'src/*.js': ['webpack', 'sourcemap'],
      'spec/*spec.js': ['webpack', 'sourcemap']
    },
    ...

We need to indicate that when webpack "preprocesses" our code for testing, it should use the sourcemapping tool we set up in webpack. The reason sourcemapping isn't included by default is because it is labor intensive and will slow down the execution of your tests. However, the benefits we gain by using it, especially when writing our tests for the first time, is well worth the extra processing time.

Excluding Specs from ESLint

There's one other thing we need to fix. Our linter will get mad at us because it doesn't understand Jasmine syntax. We don't want to lint our tests anyway! This involves a small change to webpack.config.js:

webpack.config.js
...
module.exports = {
  ...
      {
        test: /\.js$/,
        exclude: [
          /node_modules/,
          /spec/
        ],
        loader: "eslint-loader"
      }
    ]
  }
};

We just update the exclude section for our ESLint loader so that it also excludes our spec files. Note that we're now using an array because we have multiple things we need to exclude.

If any tests are failing, you’ll get a backtrace. Best of all, your tests will run continuously in the terminal until you choose to stop them (you can do so with Ctrl + C). At any time, you can refresh http://localhost:9876/debug.html and see the latest results of your tests.

We’ve only scratched the surface of what we can do with testing in JavaScript, but there’s more than enough to get started. If you’re interested, you can also explore e2e testing (integration testing) with tools like Protractor.js (developed for Angular) and Nightwatch.js. e2e is short for end to end; testing e2e means writing integrated testing that actually tests how your code runs in the browser with user input. Regardless of whether you explore e2e testing, you should fully unit-test your JavaScript applications.

Now it's time to actually start writing tests!