5 February 2014

Unit Testing AngularJS

It's not always obvious how to write automated tests for the different components in AngularJS, so I'd like to share some of my techniques for testing AngularJS applications.

Automated Testing Stack

If you've got absolutely no automated testing setup at all, then I recommend looking at using one of the following to give you some scaffolding: angular-seed, Yeoman, or ng-boilerplate.

Here's a quick overview of each piece of the unit testing stack.

Karma: The Test Runner

Karma does the work of starting our browser(s), running the tests, and reporting the results in whatever format we desire.  It can also handle pre-processing code for doing things like compiling CoffeeScript or injecting Code Coverage markers.

Jasmine: The Test Framework

We need a format to write our tests in.  The default one used by the AngularJS community, and what I'll be writing the rest of this article with, is Jasmine.  It uses a BDD (Behaviour Driven Development) style, which essentially means it tries to make your tests read like business specifications that an analyst could understand.
Here's a quick example of a Jasmine test:


The "describe()" method is used to group tests.  The "it()" method is the specification: a descriptive string for the spec, and a function for testing that spec.  The "expect(value).toSomething()" is an assertion.  You pass a value to "expect()" and then you run what's called a 'matcher' method against it.  You can also run setup and teardown code using "beforeEach()" and "afterEach()" methods.
Karma has an adapter for interpreting the results from Jasmine, which it can then feed into various reporters.  So if Jasmine isn't your poison, chances are there's an adapter out there for whatever testing framework you prefer.

ng-mocks: The Helper Library

If you've ever downloaded AngularJS as an archive, you may have spotted the angular-mocks.js file.  This contains the ngMock module, which provides a set of helper functions to make your testing life easier.  Particular the inject() method which you can pass a function with injectable parameters (eg. Services), and it will handle all the dependency injection for you.

Services

Alright.  Down to the actual testing.
I'm going to start with the simplest example.  This will work for anything produced with "module.value()", "module.constant()", "module.factory()", or "module.service()".


The relevant parts for AngularJS developers is "module()" and "inject()".

The best way to think of the "module()" function is that it's doing the same job as the "ng-app" directive - it bootstraps that module so you can inject it's components.  The great thing is that the scope for that module only lasts for a single test.  So changes you make in one test won't have an effect on the next test.

The "inject()" method hooks into Angular's dependency injector.  So you can pass it a function with your dependencies, and it will handle their injection for you.  Although it won't work if the module those components belong to has not been loaded yet by "module()".

Here's a more verbose example; a pattern which I often use myself:


Handling Dependencies

I try not to make any function calls in my service constructor.  This makes things simpler if, for example, I depend upon another service which I want to mock during testing (It is called "unit" testing for a reason).  Lets say "myService.myMethod()" called "anotherService.anotherMethod()".  Rather than test what "anotherMethod()" does within the test for "myMethod()", I just want to confirm that it gets called.  I can do this by getting an instance of "anotherService", using "inject()", and replace "anotherMethod()" with a spy which tracks if and how it gets called:


If "myService" was using a method from "anotherService" in it's constructor, that would make things trickier, but not impossible, to test.  Services are constructed only when they're first injected (no point constructing something that's not even being used).  So the trick is to inject "anotherService" first, set up your spy, then inject "myService".


You couldn't do this if your constructor was calling one of it's own methods (eg. "myService.init()").  So when you're writing a complex constructor for a service, or controller, you really need to sit back and think "How am I going to test this?".

Filters

Filters are just functions.  The key to testing them is to get yourself a reference to those functions.  This is simple with "inject()".  You should set a parameter with the name of your filter with the suffix "Filter".  Here's an example:


Alternatively you can use the "$filter()" function, like so:


Controllers

Now we're getting somewhere interesting.  Controllers are different to services in that their dependencies aren't just services - they can be "resolve" values, such as "$scope".  On top of that, not all controllers just attach properties to "$scope".  Some will attach properties to themselves through "this".  2 examples are using controllers for directive to directive communication, and the new 'ng-controller="MyCtrl as scope"' optional syntax for controllers being introduced in AngularJS v1.2

So we need a way to inject specific dependencies into our controller, and then we (may) need a reference to the instance of that controller function.  The way to achieve this is to use the $controller service:


That works if you're not doing much with the $scope except for attaching properties.  But what if I'm using some of the built in functionality for scopes like "$watch", "$on()", "$broadcast()", or  "$emit()"?  You could create spies to mock all these things.  I personally like to use a real $scope object.  So how do I get one?  Inject "$rootScope" and call "$new()" on it:


Here's my template for controller tests:


Directives

The key to testing directives is to use the $compile service to compile a DOM element which includes your directive.  $compile will trigger your directive's code, and you can then start querying the DOM element and scope to test it's behaviour.
Here's a simplified version of the ng-hide directive (Similar to the original, but without $animate support):


To test it, we need to compile the directive, and then test how the element reacts when we change the scope or trigger use actions.
Here's a test taken straight from the AngularJS source code (The best source for writing and testing directives):


Walking through it, first it uses jqLite/jQuery to create a DOM element with the directive.
Then it passes the element to the $compile service, along with a scope object (in this case $rootScope), which runs the code for any directives it finds.
Then it tests that the element is still visible, since "exp" is undefined and therefore falsy.
Then it sets "exp" to "true", triggers the digest loop so that the $watch gets run, and then tests that the element is now hidden.

Most of your directive tests are going to follow this pattern some how:
  1. Create a DOM element with your directive.
  2. Pass it to $compile(), along with a scope object.
  3. Change the scope.
  4. Query the DOM for changes.

Providers

A provider is really no different from a service, except that it requires a special "$get()" method for "providing" the dependency, and it can exist during the "config" phase of AngularJS' lifecycle.
The trick is getting a reference to the provider in pristine condition (ie. Before "$get()" is called).  The way you do this is using the "module()" helper function, provided by ng-mocks:

In this scenario we have a provider called "myServiceProvider", which belongs to module "myModule".
We use the "module()" function to instantiate "myModule", and then get a reference to "myServiceProvider".
However, calling "module()" alone is not enough.  It doesn't actually do anything until "inject()" is called.  So we just call "inject()" with no dependencies, meaning "myServiceProvider.$get()" has still not been called.

Conclusion

AngularJS has been built from the ground up with testing in mind, but it's not always immediately obvious to new comers as to how they might test a particular component.
But once you know the trick, the pattern to adopt and the services to call, there's nothing to stop you writing a suite of tests you can rely on.
Then library upgrades become a trivial matter of: drop in the new version, run the tests, and fix any failures.  I couldn't use the weekly AngularJS builds without it.

22 January 2014

5 Reasons to use AngularJS

Post republished to https://legacy-to-the-edge.com/5-reasons-to-use-angularjs/

I've been using AngularJS for about a year now, and I think it's safe to say that it's one of the best things to happen to me as a web developer.  It's made UI development faster, safer (ie. testable), and more enjoyable for me then ever before.  And by studying it's source code and taking up it's techniques and philosophies, it's made me a better programmer outside of AngularJS too.

If you haven't heard of AngularJS, go to their home page right now, read through the blurbs, have a quick toy with the demos, then come back.  If you're not sold on it already, read on and I'll tip the scales for you with 5 reasons to start using AngularJS for all your web development work - especially if you're working with a legacy system.

1. Less work for the same result

The story behind how AngularJS went from a hobby project developed solely by +Miško Hevery to being a Google sponsored open source project is that Misko went to his manager, +Brad Green, and said "You know, I bet I could rewrite our current project in Angular in 2 weeks.".  It took 3 weeks and the lines of code went from 17,000 to 1,500.

The key behind such a massive reduction in code is AngularJS's declarative approach to data-binding on the UI.  Lets start with a simple example.  I want a text input to update the message in another part of the DOM. Here's how I might do it:


Now compare to the same thing done in AngularJS:

Done.
I have simply declared "Here's my application, here's an input that binds to 'message', and here's somewhere to output 'message'."
The "How" is left entirely to the directives to determine.

It's a contrived example, but it demonstrates the advantage of a declarative approach: Stating the "What?" and then letting the framework handle the "How?".

The same can be said for dependency injection.  When I'm writing a controller, I don't tell it where and how to get the '$log' service.  I just declare "I need the service called '$log'".

2. It thins out your server-side code

Angular takes care of templating and routing your views, which is generally a non-trivial chunk of your server-side processing and code maintenance.  You can entirely decouple your client application, leaving the server to serve and consume JSON data.

This provides a great opportunity if you've been thinking of switching or experimenting with different backends.  Lets say your backend is written in Ruby on Rails, but you're interested to see how it would perform with Groovy on Grails instead.  You can write your front-end in Angular with a RESTful server API, then swap out your backend with something that serves that same API.  It's no small thing to rewrite a whole back-end, but it's a hell of a lot easier when you don't need to worry about the UI.

3. Testing is trivial

AngularJS was written from the ground up to be testable.  Dependency injections lets you slip in mock versions to work with.  Directives can be triggered by using $compile on HTML strings.  HTTP responses can be imitated with a mock $httpBackend service.  And all of this without actually attaching anything to the DOM, meaning your tests run lightning fast.

The barriers to testing AngularJS applications are only as high as you choose to make them.  You're writing less code, so you have the time to write tests from the start.  Sometimes you may feel that writing your mocks takes so much effort that it's impractical.  That's a sign that either you need to take a closer look at the documentation for Jasmine 'spies', or you need to rethink your design ("Why does this single unit of code need so much 'stuff'?")

4. Your user experience is more responsive

You don't always notice just how much time is taken changing from one page to another, on the web.  Sometimes that's to be expected, such as when moving from a completely different 'site' to another.  But when you're moving from one page to another, within the same site, with almost the exact same headers and footers, CSS includes and Javascript libraries, it should only take as long as is required to load the HTML which has changed.

With a SPA (Single Page Application), like Gmail, you have some initial load time on your first visit, but then content changes within that application are near instantaneous.  You can manage this easily with AngularJS's built in URL routing and ng-view directive.  And if that's not sophisticated enough for your needs, you can use ui-router to define your application in terms of a state machine with nested views.

5. Great community

Last, but far from least, is the AngularJS community.  There's no shortage of resources for learning Angular (eg. egghead.io, yearofmoo), places to ask questions or just 'stay in the loop' (eg. Google Groups, G+ Commmunity, IRC Channel, Meetups), and open source projects for improving Angular development (eg. Angular UI, Yeoman generator, ng-boilerplate).  Everyone I've interacted with in the community has been helpful and constructive with their criticisms.  Angular would not be seeing the success it has without such positive community contributions.


If that hasn't convinced you - power to you, you must have a damn nice set up already.
If your curiosity is piqued, but you still have concerns, visit the G+ Community or IRC channel.

Cheers,
Jason Stone