AngularJS TDD experiences?

I’m woking on a small Angular/MEANJS app to learn how a it works. I have a very (very) simple app up and running, based on the MEANJS book with some extensions. Now I want to start implementing some tests and get started on TDD.

That’s proving to be challenge. I’m using Karma and Jasmine (I’ve used Jasmine before for testing some straight-forward Javascript with no DOM interactions - GitHub - JESii/CtCI-with-JavaScript-TDD: Cracking the Coding Interview in JavaScript with Jasmine) but with the module injection requirements for AngularJS I’m pretty much lost.

I’ve googled around and all I find are the simplest of tests, like:

  it('should perform action 1', function() {
    expect(t).toBeTruthy();
  });
  it('should perform action 2', function() {
    var expectedValue = true;
    expect(t).toEqual(expectedValue);
  });

and I keep running into the problem that code I do find is based on earlier versions of Angular so they’re doing things like injecting $scope or $httpProvider which aren’t used in my app and don’t already exist.

Just for the record, I have looked at the AngularJS documentation (and will continue searching), but didn’t really find much there.

Can anyone point me to some solid resources or examples for making some progress… I’d sure appreciate it.

I’ve been trying to do more TDD in angular, but a lot of times I still write the tests after, especially with protractor and directives.

I haven’t found very good testing resources for ng either. There’s an old ng-newsletter post that does a bit of testing.

Instead of testing if the dep was injected, I tend to just mock the service/factory and spy on it in my controller tests.

If you aren’t using $scope, you might just be able to inject _$controller_ before your test, do var controller = _$controller_('myController', {}) and then test methods on controller instead of $scope?

Got any example code you could post that you want to test?

Thanks, Evan… makes me feel a bit better that I didn’t miss a bunch of obvious stuff (although it would have been nice :=). I did just find a book published very recently “AngularJS Test-Driven Development” which I’ve started working through so hopefully that will help. I can see that they use the spyOn approach that you mentioned, and they use Protractor as well.

Question for you: where did you find that I need to inject _$controller_ version just plain old $controller? Or $locationProvider versus $location, and $scope versus $rootScope, for example, as I’ve seen elsewhere? It could just be names that people have chosen or ones that have specific meaning… not clear to me with all the dependency injection going on. The more I work with Angular, the more I’m coming to believe that – just like Rails – there’s a whole bunch of magic going on, but with configuration over convention (or so it seems to me) it’s got a higher learning curve than Rails.

I’ll work on posting some code over the weekend when I have more time. Thanks again for your comments!

It’s in the docs here, but not explained terribly well. I think most of the angular internals, except for $injector for some reason, just get wrapped in underscores in tests.

The $provider vs $service vs $factory gets into a part of angular that I don’t really understand and don’t really enjoy. IMO it’s starts to feel like stuff that only the framework developers should care about and not stuff that people using ng to build apps should have to worry about.

My understanding of rootScope vs Scope is that when you create an ng-app it creates its own $rootScope that all the other $scopes inherit from. When you’re inside a controller, you are working with that controller’s $scope, but you can still $broadcast events on the $rootScope if you need to communicate something across the application (although this has turned into a bad practice and should probably be done with a factory instead).

When you’re testing, especially with controllers and directives, it’s very common to have to call $rootScope.$digest() to trigger angulars digest cycle and update any scopes that may have changed.

I did this yesterday:

in my controller there’s a function that gets called when the controller is created to pull some data from local storage:

cacheService.getOne('userCache', $stateParams.userId).then(function(resp){
    $scope.user = resp;
});

in order to test the value of $scope.user angular needs to know it changed, so there needs to be a digest cycle:

describe('myCtrl', function(){
  var fakeParams;
  beforeEach(function(){
    fakeParams = {userId: 99}
    module('myapp');
    module(function($provide){
      $provide.factory('$stateParams', function(){
        return fakeParams;
      });
    });

    inject(function(_$rootScope_, _$controller_, _$httpBackend_, _$q_,
      _cacheService_){
      $rootScope = _$rootScope_;
      $httpBackend = _$httpBackend_;
      $q = _$q_;
      // mock all template calls
      $httpBackend.when('GET', /\.html$/).respond('..')
      $scope = {};
      cacheService = _cacheService_;

      userCache = {
        userId: 99,
        name: 'super man'
      }
      // mock this before instantiating controller since it's called on init
      spyOn(cacheService, 'getOne').and.callFake(function(){
        var defer = $q.defer();
        defer.resolve(userCache);
        return defer.promise;
      });

      controller = _$controller_('myCtrl', {$scope: $scope});
    });
  });

  describe('$scope.headerTitle', function(){
    it('provides a title', function(){
      expect($scope.headerTitle).toEqual('Confirm');
    });
  });

  describe('$scope.user', function(){
    it('loads the correct user from cache', function(){
     // force a digest so $scope.patient gets updated, otherwise returns undefined.
      $rootScope.$digest();

      expect(cacheService.getOne).toHaveBeenCalledWith('userCache', 99)
      expect($scope.patient.name).toEqual('super man');
    });
  });
});

It’s too much code for me to test in angular, I’m basically just killing time waiting for ember 2.0 this summer haha. Hope that helps!

OMG… And they say that Angular was designed from the ground up to be easy to test: Hah!

Thanks very much for the info; I may have to rethink where I’m spending my energy!

yeah, I have mixed feelings about that…dependency injection does make it easier to mock/swap pieces of the application, but man it gets even worse when you’re testing custom directives and have to worry about htmlToJs, $compile and inline html in your js specs.

that said, we use angular for ionic and it’s been great to build an ios/android app with stuff we already know.

but I dunno, being able to type ember g controller mycontroller and have the test setup for you and ready to go is pretty great!

Well… I finally got some basic specs working, @evan. Not TDD by a long shot, but have enough of a working test harness so that I can try things one step at a time and see what happens. Now that I’ve got this tiny bit working, it makes much more sense than it did at the beginning.

“The only things that are difficult are those I know nothing about” [Mark Twain]

Anyway, I started off with something like this, copied from somewhere…

describe('basic running display', function() {
  beforeEach(function() {
      module('runningCtrl')
      });

  beforeEach(function() {
      inject(function( $controller) {
        running = $controller('runningController');
      })});

  it('should have items available', function() {
  });

});

And eventually, one console.log() statement at a time, got to something like this…

describe('basic running display', function() {
  beforeEach(function() {
      module('runningCtrl')
      });

  beforeEach(function() {
      inject(function( _$location_, $controller) {
        var $location = _$location_;
        console.log("inject function: path = " + $location.path());
        console.log("changing path: " + $location.path('/runnings'));
        console.log("new path: " + $location.path());
        running = $controller('runningController');
        var keys = Object.keys(running);
        console.log("running at beforeEach " + keys.length + " / " + keys + " / " + running.plans);
        console.log("some running details '" + running.plans[1].details.title + "'");
        console.log("current location " + $location.path());
      })});

  it('should have items available', function() {
    console.log("A running title " + running.plans[0].details.title);
    expect(running.plans[0].details.title).toEqual("My first plan");
  });

});

It still isn’t much, but at least I now have a running test that I can use to build up my knowledge. Your comment about _$controller_ and the underscores was very helpful - thanks! - and realizing that I can inject pretty much anything in the inject function, even if it’s not used in the actual controller, is also a breakthrough of sorts: if I need it, just inject it.

Thanks again!

cool! you may want to consider moving anything using $location into tests separate from the controller spec since the controller is instantiated by the router config. I made some notes on this a while back when I was figuring out how to test angular, they might be helpful/a little outdated.

Definitely looks interesting; I’ll dig into it in a bit… thanks.

Right now, I’m pretty jazzed… I (finally) got my first Protractor tests working. Not sure how “right” it is, but it works.

describe('Given I have a Runner index page', function() {
  var scope = {};
  beforeEach(function() { // Login to the site
    browser.get('http://localhost:8081/login');
    browser.waitForAngular();
    element.all(by.model('login.loginData.username')).sendKeys('jon');
    element.all(by.model('login.loginData.password')).sendKeys('password');
    element.all(by.buttonText('Login')).click();
  });
  describe('And it has the list display', function() {
    describe('When I review the page', function() {
      beforeEach(function() {
        browser.get('http://localhost:8081/runner');
        expect(element.all(by.css('.btn')).count()).toEqual(1);
      });
      it('has an Add an Item link', function() {
        var addItemLink = element.all(by.css('#new-run-item'));
        expect(addItemLink.getText()).toEqual(['NEW RUN ITEM']);
      });
    });
  });
});

Interesting side note: while searching for information about Protractor, I ran across what might be one of ‘your’ pages? Thanks for your support!

Hi, @evan. I took a look at your blog post – you lost me at defer.resolve(data.geonames[0]) :smiley: That’s some pretty advanced stuff which I will have to work on.

I understand the concept of promises (at least I think I do) but I get lost in the details and the $q (?:provider|service) still seems rather foreign. So that statement says to me that data.geonames is a promise which you must then resolve; but then you defer it (another promise) so that it’s available upon success… or something like that. Once created, these things make some sense, but creating them is still black magic.

But I think this is a great example for me to work through; I expect I’ll hit some of this in the app I’m working on so thanks again for this fruitful example.

good article on promises

$q is just a promise implementation. $q.defer() creates it, defer.resolve(someData) tells it what to return and defer.promise returns the whole thing so you can call .then on the function. At least that’s how I understand it, I’m not really a good frontend person.

The code I used that for that article is up here, with a demo. it’s from a course i did on angular last year, it still kinda works ;).

Interesting post about promises; I may actually have seen that one some time back, but it was still a good conceptual review. In addition, I found this post about promises in AngularJS which goes into more detail.

I also found a post about TDD in Angular here. Lots more detail / useful, although at the end the author assumes greater familiarity with the framework than I can muster at this point. More to study and learn from.

In the meantime, I am slowly (actually ‘glacially’ might be more descriptive) adding protractor tests and using it to create a simple form and the associated model.