Wednesday, June 26, 2013

baseapp: Server-Side Unit Tests

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

  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 server-side unit tests using jasmine-node and istanbul for code coverage. The grunt task to run the tests is 'grunt jasmine_node_coverage'. The tests always run with code coverage enabled.

Unlike the client-side unit tests I do not use any third party grunt plugin to execute this grunt task. Let's go to the configuration!

    jasmine_node_coverage: {
        options: {
            coverDir: 'public/coverage/server'
            , specDir: 'spec/server'
            , junitDir: './build/reports/jasmine_node/'
        }
    }

Not a lot here - 'options.coverDir' is the directory where coverage information will be dropped, 'options.specDir' is where all of the server-side unit tests live, and finally 'options.junitDir' is where the JUnit XML test output (one file per suite) is placed.

Jasmine-node works almost identically to client-side jasmine. The biggest (only?) difference is how asynchronous tests are handled. While both jasmines match 'runs()' and 'waitsFor()' functions to handle asynchronous tests, jasmine-node also provides 'jasmine.asyncSpecDone();' matched with 'jasmine.asyncSpecWait();' to handle async tests easier.

Let's take a look at the userSpec.js suite. This file unit tests the authentication code in routes/user.js. Unlike the client-side tests these tests are not run in a browser and objects under test are pulled in via the normal 'require' method.

In this case I expect the redis database to be up, you can use jasmine spies to mock it all out, but instead I select redis database '15' to not interfere with production data (redis has 16 databases named 0-15, by default you get database 0 unless you change it as I do in the 'select' call).

Also I have an 'afterEach' method that completely blows out the contents of the redis database after each test so I'm guaranteed each test gets a fresh database (redis.flushdb()).

The async tests themsevles are very straightforward, just call methods and verify resopnses, easy peasy.

Application Architecture

A quick note about ease of testing, you will note in user.js, I was clear about separating HTTP from the functionality of the module to make testing easier. I was very careful to not mix dealing with HTTP-protocol based values with authentication routines. I specifically did not want to mix up HTTP with authentication to make testing easier. This way I do not have to mock/deal with HTTP when testing the authentication routines. Be sure to keep separation of concerns in mind while writing your code. Writing tests first will help keep your code clean.

Code Coverage

Istanbul has great integration with jasmine-node, looking further down in the Gruntfile you can see the implementation of the jasmine_node_coverage task. Simply running istanbul with jasmine-node will automatically generate code coverage and the coverage output is written to 'public/coverage/server/lcov-report/index.html' in fancy HTML.

No comments:

Post a Comment