Grunt testing

Preface: This is the kind of guide we wish we had when we were starting to learn Grunt. It's lengthy, so if you want to skip that, there's a handy gist that goes along with it so you can share in our process.

This guide is separated into 3 parts. This is Part 2 and covers our testing process. We previously published Part 1, which covers our local development process. And we conclude the series with our deployment process.


This section is shorter than the other two. Let's just dive right in!

grunt test

As you might except, tests are also central to our process. We lint our code with jshint, run unit tests with karma, and verify everything works with end-to-end testing using protractor.

Commands we've made available to run:

  • grunt test - runs our basic unit tests
  • grunt test:jshint - lints our code
  • grunt test:e2e.update - installs/updates WebDriver
  • grunt test:e2e.start - starts a WebDriver instance
  • grunt test:e2e - runs our e2e tests
  • grunt test:suite - runs our suite of tests

As you can see, we have a lot going on in our little grunt test call. It's pretty simple once you learn it, but the code might look gnarly to those of you who aren't used to Grunt. That's OK, we'll go through it.

So let's look at how we register grunt test.

grunt.registerTask('test', 'Execute tests', function (target) {
  // Update webdriver-manager on 'grunt test:e2e.update'
  if (target === 'e2e.update') {
    return grunt.task.run(['exec:updateWebDriverManager']);
  }

  // Start webdriver-manager on 'grunt test:e2e.start'
  if (target === 'e2e.start') {
    return grunt.task.run(['exec:startWebDriverManager']);
  }

  // Execute karma unit tests on 'grunt test' or 'grunt test:suite'
  if (typeof target === 'undefined' || target === 'suite') {
    grunt.task.run([
      'clean:server',
      'injector',
      'concurrent:test',
      'autoprefixer',
      'karma'
    ]);
  }

  // Execute protractor e2e tests on 'grunt test:e2e' or 'grunt test:suite'
  if (target === 'e2e' || target === 'suite') {
    grunt.task.run(['exec:runProtractor']);
  }

  // Execute jshint on 'grunt test:jshint' or 'grunt test:suite'
  if (target === 'jshint' || target === 'suite') {
    grunt.task.run(['newer:jshint']);
  }
});

Without any targets set on our task, we run our unit tests by default. You can see the steps above if (typeof target === 'undefined' ...). These are pretty similar to our regular grunt serve task but with just two differences: no connect task (because karma runs its own) and the grunt karma task is added!

Our grunt karma configuration simply looks like this:

karma: {
  unit: {
    configFile: 'test/karma.conf.js',
    singleRun: true
  }
}

We might talk about our karma configuration in the future. For now if you need help, the karma configuration file docs are pretty good. Still need help? We'd be delighted. Just tweet us.

grunt test:e2e.update

Since it's at the top, let's talk about our end-to-end testing.

We use protractor to run our e2e tests. It happens to have a dependency on WebDriverJs so we have this task target set up to install or update the local installation of it.

To run the update task, we use grunt-exec and have it configured below:

exec: {
  // Download and update webdriver-manager script within protractor node module
  updateWebDriverManager: './node_modules/protractor/bin/webdriver-manager update',
}

grunt test:e2e.start

In order run our tests, WebDriverJs has to be started. We do this in a separate terminal tab since it has to be run concurrently with our local server and our test task. Here you can see our quick way of executing it:

exec: {
  // Start webdriver-manager server
  startWebDriverManager: './node_modules/protractor/bin/webdriver-manager start',
}

grunt test:e2e

We use this to actually run our protractor tests.

exec: {
  // Run protractor e2e tests
  runProtractor: './node_modules/protractor/bin/protractor test/protractor.conf.js'
}

Sidenote: We currently have to open 3 tabs to run our e2e tests. One for our local server, one for our WebDriverJs, and one for our tests. While writing this, I realized grunt-concurrent may be a way around this. Do you have a different one? Let us know, we'd love to reduce the steps needed to run this!

grunt test:jshint

Conveniently runs all of our our JavaScript files through grunt-contrib-jshint. We saw it in use in our watch file but didn't talk about it much. Here's the configuration:

jshint: {
  options: {
    jshintrc: '.jshintrc',
    reporter: require('jshint-stylish')
  },
  all: {
    src: [
      'Gruntfile.js',
      '<%= yeoman.app %>/scripts/**/*.js',
      '!<%= yeoman.app %>/scripts/**/*.test.js'
    ]
  },
  test: {
    options: {
      jshintrc: 'test/.jshintrc'
    },
    src: [
      '<%= yeoman.app %>/scripts/**/*.test.js',
      'test/e2e/**/*.js',
      'test/karma.conf.js',
      'test/protractor.conf.js'
    ]
  }
}

The global options above specify the .jshintrc file which is where you can easily specify your project's rules. We also changed our reporting to jshint-stylish because it helps distinguish breaking problems from non-breaking.

Our grunt jshint:test task uses a different .jshintrc, mostly to declare different global variables that are used.

The src properties are fairly straightforward again.

grunt test:suite

Sometimes we want to run all of our tests at once (like before a deployment). That's why it's in various if statements. This task runs all of the tests we just discussed (linting, unit, and e2e).

Wrapping up our test suite

Above, we discussed how we can run code linting, unit tests, and end-to-end tests separately or all at once with our grunt test:suite command. If you've never run tests on your code, it's a good idea to start. Even the simple jshint can save you hours of searching around for that one character that's out of place. Unit and E2E tests are an excellent early warning system before a bug finds its way to your production website.

To help you combine everything that's above, we've created a gist with our entire Gruntfile.js and package.json.

In the previous section, we discussed how you can use Grunt to automate tasks while you're writing code. Up next, you'll learn how to deploy all that code you've written and thoroughly tested.

Mark Curphey, Vice President, Strategy Mark Curphey is the Vice President of Strategy at CA Veracode. Mark is the founder and CEO of SourceClear, a software composition analysis solution designed for DevSecOps, which was acquired by CA Technologies in 2018. In 2001, he founded the Open Web Application Security Project (OWASP), a non-profit organization known for its Top 10 list of Most Critical Web Application Security Risks. Mark moved to the U.S. in 2000 to join Internet Security Systems (acquired by IBM), and later held roles including director of information security at Charles Schwab, vice president of professional services at Foundstone (acquired by McAfee), and principal group program manager, developer division, at Microsoft. Born in the UK, Mark received his B.Eng, Mechanical Engineering from the University of Brighton, and his Masters in Information Security from Royal Holloway, University of London. In his spare time, he enjoys traveling, and cycling.

Love to learn about Application Security?

Get all the latest news, tips and articles delivered right to your inbox.

 

 

 

contact menu