Skip to content

Instantly share code, notes, and snippets.

@wildlyinaccurate
Last active December 27, 2015 23:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wildlyinaccurate/6f3bc2e0c2de8bf0b457 to your computer and use it in GitHub Desktop.
Save wildlyinaccurate/6f3bc2e0c2de8bf0b457 to your computer and use it in GitHub Desktop.
Notes for my talk on Automated JavaScript Testing (http://slid.es/wildlyinaccurate/automated-javascript-testing)

Historically in IPC Digital, it's been pretty difficult to automate JavaScript tests. The main reason has been that most JavaScript testing tools require Node.js which, up until recently, our buildslaves didn't have. The good news is that we now have a CentOS 6 buildslave with Node.js, which means that we've run out of excuses for not testing our JavaScript!

So today I'm going to tell you about a few tools that make testing JavaScript super easy, and I'm going to show you how you can use those tools to test a typical JavaScript snippet.

I'll also briefly touch on automated cross-browser testing, and how to get your tests running in Jenkins.

What I'm hoping is that after this, you'll be convinced that regardless of how difficult you think it will be, it's actually really easy to get some JavaScript tests running for your website. I'm hoping that in the not-so-distant future, every new piece of JavaScript that we write will be covered by a test.

**

Right. Testing JavaScript on sites like ours is easier said than done. Unlike our server-side code, which is organised nicely and split into easily-testable classes... Client-side JavaScript is almost always written as individual snippets which are scattered across the website. I bet most of the JavaScript you have on your sites looks something like this:

[>]


It's really easy to look at this and think "it's going to be too hard to test". None of the code is available in the global scope, and it's all wrapped up in this jQuery ajax call anyway.

Luckily, some pretty smart people have figured this out already. There are a bunch of amazing tools out there which not only make testing this code possible, they make testing this code relatively easy.

So let's write a test for it.

[>]


I'm going to be using Mocha, Chai, and Sinon to write these tests. There are plenty of other popular choices available, for example Jasmine, QUnit, Buster.JS... As far as I'm concerned it doesn't matter what you use, as long as the end result is expressive, self-documenting tests.

[v]


Mocha is a JavaScript testing framework. It organises tests and keeps track of passes & failures. You can think of it as similar to PHPUnit.

It has a bunch of different reporters, including a familiar-looking command-line reporter and an HTML reporter.

[v]

Chai is an assertion library. It has a few different syntaxes, which you can use to write some really expressive, self-documenting tests.

[v]

Sinon provides stubs and mocks for JavaScript tests.

Stubs and mocks are absolutely essential when you want to test code like the snippet I showed before. They're going to allow you to test the way your code uses third-party libraries.

In the first example, Sinon is replacing jQuery's ajax method with a stub. The stub keeps track of all the times it's been called and what arguments it was called with. This means you can write assertions like this one here, which ensures the ajax method is called with an object containing "url: /foo/bar".

Sinon also has some great utilities including a fake XHR for testing your AJAX calls, and a fake timer for testing code that relies on time-related functions like setTimeout and setInterval.

The second example here shows the fake timer being used. There's a div which is 100 pixels wide. jQuery's animate function is called to set the width to 200 pixels after 500 miliseconds. Because Sinon has taken control of the JavaScript clock, we can assert that the div's width isn't changed straight away. We can then tick the clock forward just over 500 miliseconds, and assert that the div's width has been updated to 200 pixels.

[>]


So, back to our code. How do we start testing something like this?

I mentioned before that none of this is in the global scope, which is a problem. So we actually need to make a small change to this code to make it testable.

[>]


It's not the cleanest solution, but it's going to allow us to test this code without doing some heavy refactoring.

[>]


Here's a simple test for this code. I'll take you through it.

[v]

We create a mock for jQuery using Sinon. At this point, the jQuery object isn't modified.

[v]

Now we tell the mock that it should expect the ajax method to be called exactly once. This is where Sinon actually modifies the jQuery object and swaps out the ajax method for a stub method.

[v]

...And this bit is why Sinon is awesome. After the ajax method is called, Sinon is going to look at the arguments it was passed, find the success property in those arguments, and then call it with the data we specify.

This basically allows us to keep the behaviour of jQuery's ajax method, without having to write our own complicated mock for it.

So if you're following, our code's success method is going to be called with this object containing "Message: Hello".

[v]

Now we actually run our code.

[v]

And finally, we get Sinon to verify all of the mock's expectations. If the mock is called in a way that we don't expect...

[v]

... The tests will fail. (In this example the mock is expecting to have its get method called, but it never is).

[>]


[This slide is for context]

[v]


We could take our mock even further and check that our code does actually make a POST request.

[v]

And we can go to any level of detail here, depending how strict we want the test to be.

To be honest, I'd be cautious about going into this much detail. Mocks are really powerful because they allow you to test stuff like this, but putting too much detail into your mocks can make your tests really brittle.

[>]


Right, what if we want to take this a step further and test the success callback? We could write a test to make sure that the response is put into the DOM and shown to the user.

[>]


We're going to use Sinon's stub functionality to test this.

[v]


What's happening here, is we're creating a $node stub object. This stub contains the text and show functions that we're expecting to be called.

Then we stub the global "jQuery dollar" and force it to return our $node stub.

So, just to clarify, [<] $('.foo') is going to return [>] this $node object. And because we've stubbed this $node object, we can track how it's been used...

[v]

...Like this.

So we can assert that $node.show has been called exactly once. And that $node.text has been called exactly once, with "Hello" as the first parameter.

[v]


And that's it. We've got some pretty decent test coverage for some legacy JavaScript.

...

So far I've just run the tests with Mocha on the command line, which means the tests have been running on Node.js. The next step is to get them running in an actual browser.

[>]


Now, I don't know what the official stance on this is, but I've been using Grunt to build JavaScript projects. And I've been using Karma to run the tests.

Grunt is a task runner. It's essentially the JavaScript equivalent of Phing, only much better. There are hundreds of plugins available for Grunt, and you can write your own really easily.

Karma is a JavaScript test runner. It was created by the AngularJS team to allow them to test their code on real browsers, on real devices.

Getting Grunt and Karma set up requires a little bit of boilerplate, which I'll go through now. If you want some examples to help you get set up, there are a few projects in our GitHub that you can copy from as well.

Otherwise...

[>]


... I recommend checking out Yeoman, which can generate stuff like this for you.

[>]


Right, the first thing is to define some dependencies in package.json and run npm install.

[v]

Then you define some tasks in a Gruntfile, which is the equivalent of Phing's build.xml.

[v]

And finally we configure Karma. The things to note here are that you need to require any dependencies (in this case jQuery), as well as your JavaScript source, and the tests.

[v]

Then you can run Grunt.

[>]


So I mentioned that Karma can run on any browser, on any device. The Karma team have written launchers for a bunch of browsers.

[v]

All you need to do is specify which browsers to run in the Karma configuration. The only catch is that these browsers need to be installed on the system that is running the tests, which means that you can't run cross-browser tests on the buildslaves. However...

[v]

...There are also Karma launchers for Sauce Labs and BrowserStack, which are essentially cross-browser-testing-as-a-service tools.

I don't think that we have an account for either of these services, but I'd like to think that we could get an account if we start running more JavaScript tests.

But if all of that still doesn't cover your needs, you can get Karma to launch a custom browser or script, or you can write your own launcher plugin.

[>]


Getting this all running in Jenkins is super easy. You just point Jenkins at your repository, and get it to run npm install and grunt.

[>]


So at this point, you've got a job in Jenkins in which you can install any NPM module, and run it with Grunt.

This is pretty powerful, and you can do a lot with it.

[v]

You can generate JSHint reports.

[v]

You can generate JSUnit reports from Karma.

[v]

You can even write your tests in CoffeeScript if you're that way inclined.

[>]


And when you've got your tests running, you can display this sweet-as build badge on your repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment