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.