Tuesday, June 25, 2013

baseapp: Client-Side Unit Tests

baseapp provides all the boilerplate to get your JavaScript web application started off right, this Part 2.

  1. Intro to baseapp
  2. Client-Side Unit Tests
  3. Server-Side Unit Tests
  4. WebDriver Integration Tests
  5. Other Grunt Goodies
  6. Authentication
  7. Continuous Integration
  8. Administration
(or binge read them all on the baseapp wiki!)

baseapp handles clide-side unit tests using jasmine and istanbul for code coverage. The grunt task to run the tests is 'grunt jasmine'. They always run with code coverage enabled.

Test Configuration

Let's look at the configuration in the Gruntfile:

    jasmine : {
        test: {
            src : 'public/javascripts/**/*.js',
            options : {
                specs : 'spec/client/**/*.js'
                , keepRunner: true  // great for debugging tests
                , vendor: [ 
                      'public/vendor/jquery-2.0.2.min.js'
                    , 'public/vendor/jasmine-jquery.js' 
                    , 'public/vendor/dust-core-1.2.3.min.js' 
                    , 'vendor/bootstrap/js/bootstrap.min.js'
                ]
                , junit: {
                    path: "./build/reports/jasmine/"
                    , consolidate: true
                }
                , template: require('grunt-template-jasmine-istanbul')
                , templateOptions: {
                    coverage: 'public/coverage/client/coverage.json'
                    , report:   'public/coverage/client'
                }
            }
        }
    },

Here is what is happening - the grunt-contrib-jasmine plugin collects all of your client side JavaScript (the 'src' property), all of your test files ('opitons.specs') and any other JavaScript files we need to execute our tests ('options.vendor') and creates a single HTML file 'SpecRunner.html' which is loaded into a browser (phantomjs). The grunt jasmine plugin automatically adds the jasmine client-side libraries to actually run the tests too. SO when SpecRunner.html is loaded into a browser (phantomjs) all of your tests are run.

The output of these tests (in JUnit XML format) is dumped into the options.junit.path directory - one XML file per test suite.

Finally the grunt-template-jasmine-istanbul package is leveraged to generate code coverage information, the HTML output of which is dumped into the 'options.templateOptions.report' directory. We also persist the 'coverage.json' file so it can be aggragated later with other tests (like server-side unit tests and webdriver tests).

Ok that's the setup - for now just know that any '*Spec.js' file placed into the 'spec/client' directory will get executed and any client-side JavaScript file you write in public/javascripts will get loaded to be tested.

Executing:

% grunt jasmine

Will run all of this stuff.

Test Files

How about actually writing the tests? First get basically familiar with jasmine if not already. Now Let's take a look at logoutSpec.js first - a suite called 'logout' is created with two tests.

The most interesting bits are the fixture setup and the jQuery AJAX spy.

Fixtures

Most client-side JavaScript manipulates the DOM, but we don't want to load in all of our application's HTML to test our code, so we use 'fixtures' instead. The 'setFixtures' call is provided by the jasmine-jquery JavaScript library we loaded in via the 'vendors' array in our Gruntfile.js. This lets us set some HTML for the following test that is automatically cleaned up after the test ends. So note I have to 'setFixtures' for each test. If your HTML is the same for each test then you should use a 'beforeEach' function so you DRY (Don't Repeat Yourself). You'll see the HTML is slightly different for each test so I wasn't able to do that here.

Jasmine-jquery and also load fixtures from a webserver, which is especially nice if you are using templates (which you are!), so you can test using your application's actual HTML. As these fixtures are very simple I did not do that here. Also beware of how the fixture loading interacts with jQuery's AJAX object which I will discuss with more detail next.

Spies

Jasmine is espcially nice for providing spies for interacting with your code's dependencies. The logout component relies on an AJAX call to actually log the user out, but while unit testing we do not have a web server running so we need to mock out the AJAX call. We do that using the "spyOn($, 'ajax')" function call.

All subsequent calls that use jQuery's AJAX mechanism will instead get routed to this spy, the underlying AJAX call will NOT get executed (note jasmine does provide a way to also call through to the actualy implementation but we do not want to do that here).

The spy allows us to verify the ajax method was called with the expect arguments. Spies let us do more than just verify arguments and call through to the real underlying implementation, take a look at loginSpec.js to see more!

Search for the 'andCallFake' method - using that method you can have the spied on method execute and return any code you like! Let's take a close look at the "should handle failure default response" test case.

First I set up the HTML fixture for this test, then I create the login component I will test along with some other canned values I will be using more than once so I DRM (Don't Repeat Myself). Now I create a spy for the $.ajax method and inform jasmine whenever that method is called to actually execute the given function instead. Note my function receives the argument list, and in this case I simply turn around and call the provided 'error' callback with some canned data to test that code path. I then set some form elements and 'submit' the form. As this is all synchronous the error callback will get called via 'andCallFake' and finally I expect the error message is being shown properly. Finally note the 'toHaveText' matcher came from jasmine-jquery, which comes chock full of lots of great jQuery-specific matches for us to leverage. Be sure to check them out.

Test Files

So all of our client-side unit test files must reside in the spec/client directory and be named with the word 'Spec' in them - follow that pattern for your own sanity! As you create more component keep adding tests, they can be at any depth in the spec/client tree, jasmine will find them!

_SpecRunner.html and Debugging Tests

Very rarely things do not work the very first time. You'll note there is a grunt configuration property 'keepRunner'. I told you about the SpecRunner.html file grunt-contrib-jasmine generates each time you run 'grunt jasmine' - by default that file is deleted after all the tests complete. But if there a problem running the tests it is very hard to tell what is going on, especially as the tests are all run in phantomjs. By setting 'keepRunner' to 'true' grunt-contrib-jasmine will not delete SpecRunner.html so you can load it up into Chrome (or any other browser not named Chrome) and manually execute unit tests yourself. They will run quickly! Most importantly you can open up a JavaScript console/debugger window to really tell what is going on when your tests are not running correctly.

Test Output

Test results are output in the JUnit XML format with pass/fail information and dumped to the build/reports/jasmine/ directory with one XML file per-suite. The most interesting thing about this format is its wide support.

Code Coverage

All JavaScript files under test are automatically instrumented for code coverage information. Regardless of tests passing/failing you will get code coverage information dumped to public/coverage/client/index.html - simply point your browser and that file and bask in the coverage'ness. You can drill down to each individual file and see line-by-line output of coverage as provided by istanbul. Let this information help guide your next set of tests!

No comments:

Post a Comment