Mark Eschbach

Software Developer && System Analyst

Testing in AngularJS

Generally AngularJS applications are tested via Karma which will bind together a runner, runner frameworks (like Chai is to Mocha), the browser(s) to run the tests, and a file watcher to automatically pickup and run the testing suite.

Notes

  • Karma is capable of running the standard set of browser, possibly less the IE series. These are modeled as plugins such as karma-chrome, karma-firefox, karma-safari.
  • Angular views the world through $digest cycles. Generally when testing your controllers you need to invoke $digest on the scope of the controller or the root scope. Things get interesting when you need to test services which uses promises. These are the following methods I've found which kicks the $browser into executing the promises:

    • $rootScope.$digest(): Causes the entire system to run all outstanding defered functions. To comply with the Promise/A spec $q promises need to execute after the completion of the current event loop. When a promise is fullfilled (accpeted or rejected), the correct fulfillment closure is schedule to run on the next $digest.
    • (more research needed) $browser.defer.flush(): From ngMock to separate the application from the running browser for test. This appears to run all of the outstanding code which would use the setTimeout/2 in the actual browser.

    Recently I have been expirimenting with integrating Chai-as-promised into Mocha + Chai in Karma on a team I'm a collaborator on. So far the solutions I've come up with are:

    • Simplest Approach

      1. /* Capture the promise */ var result = somePromise().then( chain )
      2. $rootScope.$digest()
      3. result.value.should.be({some: value})

      I feel like we are breaking encapsulation in addition to adding a lot of boiler plate code.

    • A second is to use a macro function to wrap the complexity of testing to ensure all are promises are actually resolved. This also adds piece of mind we don't have to remember the boiler plate, thus reducing mental load.

      function $beforeEach( realBefore ){
      	var result = realBefore();
      	$rootScope.$digest();
      	return result;
      }
      function $it( description, realAssertion ){
      	it( description, function(){
      		var result = realAssertion;
      		$rootScope.$digest();
      		return result;
      	});
      }
      					

      For most simple cases this should work well. If there is any kind of complex logic, or forced $digests this would fall apart.

    • Ideally I would like to plug into the Chai As Promised framework to automagically invoke the $rootScope.$digest() when we have a promise. But I haven't found the time to do feasability studies on that yet.

  • I eventually need to find a good method to debug in the browser...