Something Same

Language, Expression and Design

Tuesday

05

November 2013

Purnam angular.js testing - part 1 - services

by Chris Zheng, on testing, purnam, angularjs, clojurescript

Even with the karma test runner, testing in angularjs is painful. It's one thing to be able to write a angular.js controller, its another to be able to test them. Making it more difficult is the sheer complexity of the framework. There are different strategies of testing controllers, directives, filters and injectables (values, services, factories and providers)

There are two excellent articles about testing in angular.js by Year of Moo. Here and Here. However, my eyes start hurting when I look at the test code. It is modular, it is brilliantly thought out, it is very complete... but it is seriously hard to get my head around. An example of controller testing can be found here

It took me about a couple of days to summon up the courage to even attempt to read the code. Then I realised that the tests weren't doing that much at all. Most of it was boilerplate and not that interesting. Out of about 5 lines of test code, something interesting only happened in one of them. I abstracted out all the angular.js testing code into macros.

The point I've been making on previous post is that clojurescript rocks if we really embrace javascript libraries through macros. With macros for angular.js, working with angular.js is so much clearer than in javascript.

I have put together an example project that shows how one may go about doing a simple app with tests. I'm going to take a couple of posts to explain how purnam.angular, purnam.test and purnam.test.angular work together.

The first part will be on basic testing as well as testing angular.js services. Services are the main workhorses of the application and they are relatively easy to test because they are injected into the angular environment. Here is an example of a test suite written in javascript.

You will notice that the test definitions are full of callbacks and setup code that make it quite ugly. For example:

beforeEach(angular.mock.module('App'));  

and

inject(function($appStorage) {  
...
}

These are boilerplate that can be eliminated using clojurescript macros. Two examples will be shown below:

loginDemo.Db

In the login example, we define the loginDemo module as well as a Db service simulating a database of users and their passwords:

(def.module loginDemo [])

(def.service loginDemo.Db []
  (atom {:users   {"skywalker" "luke"
                   "trump" "donald"
                   "dylan" "bob"
                   "white" "snow"
                   "winfrey" "oprah"}}))

We can now see that the test definition for the Db service is relatively straight forward:

(describe.ng
  {:doc  "Testing Alternate Db"
   :module loginDemo}

  (it-uses [Db] "Injecting Db"
      (is Db #(instance? Atom %))))

So there are two macros at work: describe.ng and it-uses.

describe.ng takes a :doc value and a :module value. it-uses injects the Db service into the test.

Another way is to introduce services is through the :inject keyword. Here is an alternative way of describing the Db service:

(describe.ng
  {:doc  "Testing Db"
   :module loginDemo
   :inject [Db]}

  (it "Is just an atom"
      (is Db #(instance? Atom %)))

  (it "Has five users"
      (is (-> Db deref :users count) 5))

  (it "Contains user: dylan, pass: bob"    
      (is (get-in @Db [:users "dylan"]) "bob")))

loginDemo.$mustache

Here is the service definition for the mustache renderer, located in the same file.

(def.value loginDemo.$mustache
  js/Mustache.render)

and its test description:

(describe.ng
  {:doc  "Testing $mustache Service"
   :module loginDemo
   :inject [$mustache]}

  (it "Can compile strings"
    (is ($mustache "Hello {{thing}}!" 
                   (obj :thing "World"))
        "Hello World!")))

These macros allow code to be about 1.5 to 2.5 times more compact than the javascript version, highlights all the essential points and makes things much easier to understand when coming back from a long holiday away.

Part 2 is now avaliable. Also be sure to check out the working examples here.

comments powered by Disqus