unit testing with angularjs

2,464 views
Skip to first unread message

Davis Ford

unread,
Dec 5, 2012, 3:40:42 PM12/5/12
to socket...@googlegroups.com
anyone else using angularjs with socketstream and found a good solution for unit testing?

angular wants to use testacular + jasmine as the test runner.  personally, i prefer using mocha, but i thought i'd try to take the path of least resistance just to get something working.

so, i basically tried setting it up like this seed project: https://github.com/angular/angular-seed

there's a test directory with tests, and some shell scripts to launch testacular, etc.

my client code is organized like:

client/
  code/
     app/
         app.js
         controllers.js
         directives.js
         filters.js
         services.js
         entry.js
     libs/
        00.jquery.js
        05.angular.js

Originally, I had everything in a single controllers.js file, where I did something like the following:

var app = angular.module( /* etc. */);

app.controller( /* etc. controller */)

Then I changed it to try to carve up the functionality into separate files like the angular-seed project does: https://github.com/angular/angular-seed/tree/master/app/js

I could not get this to work -- specifically the controllers, since they define a global function for each controller, but ss + requirejs hides this info.

So, I had to change my app.js to be like the following:

var app = angular.module('app', /* etc. */);

module.exports = app;

and then in controllers.js I require app:

var app = require('/app');

app.controller('MyCtrl', function ( /* etc. */ ) );

This works at runtime, but if I try to make a simple unit test, it blows up because 'require' is not found.

info (Chrome 23.0): Connected on socket id uuY5AmoI6IEjW5YtwpWo
Chrome 23.0 ERROR
Uncaught ReferenceError: require is not defined
at /Users/davis/git/ebike-node-server/client/code/app/controllers.js:2

I have to imagine there is a way around this....how can I get require loaded?  This is usually done at runtime by SocketStream?

Yi Wang

unread,
Dec 5, 2012, 10:48:08 PM12/5/12
to socket...@googlegroups.com
I got AngularJS Unit tests working using SS_PACK=1 and load the minified js in client/static/assets, in which 'require' is defined:

$./scripts/test.sh 
will run ./scripts/pack.sh first to only pack files (not launch server)

It's re-written in livescript

$./scripts/install.sh
will npm -g install testa...@0.5.5, which supports spec files in livescript

still WIP, will add e2e tests and maybe a readme

Davis Ford

unread,
Dec 6, 2012, 3:38:10 AM12/6/12
to socket...@googlegroups.com
Ah, brilliant idea...thanks!  

Yi Wang

unread,
Dec 6, 2012, 11:22:35 PM12/6/12
to socket...@googlegroups.com
update: got e2e tests working

Davis Ford

unread,
Dec 7, 2012, 10:37:54 PM12/7/12
to socket...@googlegroups.com
Any trick to it?  Can you post a gist -- I'm going to get my test infrastructure in order next week, so I'd love to see how you did it.

Yi Wang

unread,
Dec 7, 2012, 11:15:00 PM12/7/12
to socket...@googlegroups.com
both unit test and e2e test (for both browser and testacular) are checked in to the below repo.

1. For e2e in browser, a 'e2e' client is defined to serve the angular runner page

ss.client.define \e2e,
  view: \runner.html
  css: []
  code: ['e2e']
  tmpl: []
ss.http.router.on \/test/e2e/runner.html, (req, res)->
  res.serve \e2e


then require actual test file in 'e2e' client's entry.js
require('./scenarios.ls');

2. For e2e with testacular, the test file inside 'e2e' client is reused:
files = [
  ANGULAR_SCENARIO,
  ANGULAR_SCENARIO_ADAPTER,
  'client/code/e2e/**/*.ls'
];

Davis Ford

unread,
Dec 8, 2012, 3:47:45 PM12/8/12
to socket...@googlegroups.com
Thanks Yi - great help!

Davis Ford

unread,
Dec 10, 2012, 4:38:49 PM12/10/12
to socket...@googlegroups.com
Ok, I have unit tests working, as well, but it is not optimal.  Here are a few of the issues:

Mocha support is still lacking.  I prefer mocha, b/c I use it for server-side tests.  They've only recently added support for mocha, but up until the 1.1.1 release, it still lacks the angular.mock.module and angular.mock.inject functionality.  I ran into a number of issues with using mocha -- even talking with #angularjs on irc, still couldn't quite resolve it.

I also can't upgrade to the latest 1.1.1 release b/c of some other bugs the 1.0.3 release introduced with twitter bootstrap.

So, I dropped back to jasmine -- which is ok, I guess -- two separate test frameworks (mocha/should/sinon = server, jasmine/chai/testacular = client), but it can give one a headache trying to navigate between them all.

Still, the uglify/minified client/static/assets solution works, but it is not ideal -- doesn't promote fast iterations b/c anytime you make a change to your app code, you have to go restart SocketStream with SS_ENV=production SS_PACK=1 to regenerate the .js

In a tight test/code loop, this happens a lot.  I want to keep trying for a better way.

Pierre-Yves Gérardy

unread,
Dec 11, 2012, 6:57:52 PM12/11/12
to socket...@googlegroups.com
Isn't it possible to use the latest version of angular-mock with AngularJS 1.03? I've yet to test it, but the commit log looks innocuous: https://github.com/angular/angular.js/commits/master/src/ngMock

-- Pierre-Yves

Davis Ford

unread,
Dec 11, 2012, 10:37:52 PM12/11/12
to socket...@googlegroups.com
Each release of angular also releases angular-mock.js, so sure, you can use angular-mock.  But, if you want to use angular.mock.module and angular.mock.inject, support for mocha wasn't introduced until release 1.1.1.

I'm also hitting this bug: https://github.com/angular/angular.js/issues/1674 which is something quirky between angular and jquery.  Works fine in 1.0.2,but post 1.0.3 it is a problem.

Anyway, I've spent a lot of time on this the last 2 days -- trying to devise the best testing strategy.  IMHO - both angular and browserify via socketstream are doing the same thing client-side..which is modularizing code.

So, the simplest thing is to just stop fighting it: use angular...carve out your client side code with vanilla self-executing functions that respect the global namespace and don't require('foo') files client side with angular code, b/c it is a nightmare to test (at least today).

This way, you can use standard tools -- and not have to rely on things like packing all the assets into a single minified / uglified js file that is a nightmare to debug, and which you have to rebuild each time you make a client code change.

I want to enable: 

a) edit client code || edit test code
b) save
c) auto-run tests on change - growl reporting pass/fail

That's just my personal preference...but none of this stuff is documented anywhere, so I just had to plow through it the hard way to figure out the best way forward for a real project that wants testing.  If I had more time, I'd carve out a blog post re: learnings here, but am short on time.  Happy to discuss it further if anyone wants to.


On Tue, Dec 11, 2012 at 6:57 PM, Pierre-Yves Gérardy <pyg...@gmail.com> wrote:
Isn't it possible to use the latest version of angular-mock with AngularJS 1.03? I've yet to test it, but the commit log looks innocuous: https://github.com/angular/angular.js/commits/master/src/ngMock

--

Pierre-Yves Gérardy

unread,
Dec 12, 2012, 9:38:51 AM12/12/12
to socket...@googlegroups.com
On Wednesday, December 12, 2012 4:37:52 AM UTC+1, Davis Ford wrote:
Each release of angular also releases angular-mock.js, so sure, you can use angular-mock.  But, if you want to use angular.mock.module and angular.mock.inject, support for mocha wasn't introduced until release 1.1.1.
What I meant was: use angular-mocj 1.1.1 with angular 1.0.x... But actually, module and inject aren't supported, even in that version. I managed to get it to work, though, at least in sync mode. Here's my setup:

In app.js, I create a separate app for the tests:

if (MODE == 'development') {
  ss.client.define('tests', {
    view: 'tests.html',
    css:  ['mocha.css'],
    code: ['libs', 'app', 'tests'],
    tmpl: '*'
  });

  ss.http.route('/tests', function(req, res){
    res.serveClient('tests');
  });
}

With this config, all files and folders in code/app are available for the tests, and the code/tests/entry.js overrides code/app/entry.js

tests:
+- entry.js
+- libs:
|  +- 128.expect.js
|  +- 256.mocha.js
|  +- 320.mocha.setup.bdd.js
|  +- 384.angular-mocks.js
+- specs:
   +- test.js
 
The angular/jQuery libs are loaded in code/libs, before code/tests/libs is loaded.

mocha.setup.bdd.js contains one line: mocha.setup('bdd'); This must be done before loading angular-mocks.

The view is pretty simple. Specifically:

[classic SS header.]
<body>
   <div id="mocha"></div>
</body>

For the controller, I inject the app rather than requiring it (a trick I've read on this list before):

code/app/controllers.js:
module.exports = funcfion(ngModule) {
    ngModule.controller(...)
}

It allows me to inject as many angular modules as I want during testing. 

code/app/entry.js
foo = angular.module('foo');
require('./controller')(foo);

code/tests/specs/test.js
var bar;
beforeEach(function(){
    bar = module('bar'+counter())
    require('../controllers')(bar)
})

code/tests/entry.js
$(function(){
  require('./specs/test')
  mocha.run();
})

At last, in order to get mock.module and .inject to work, you must change isSpecRunning in angular-mock.js, line 1624:

function isSpecRunning() {
    return currentSpec && currentSpec.queue && currentSpec.queue.running
        || currentSpec; // for Mocha.
}

I don't know how reliable the latter is, but it works for sync tests.

Davis Ford

unread,
Dec 12, 2012, 10:38:06 AM12/12/12
to socket...@googlegroups.com
Pierre, thanks for the writeup -- that is an interesting way to do it -- I hadn't thought about making a separate ss.client.define('tests') ....do you still obtain live reload of the tests when code and/or test code changes?  That was my single biggest driving factor for what I wanted to achieve.  It seems like you would if you run it with nodemon and serve it as a standard webapp.  This would allow you to de-couple from the shackles of testacular...not that it is necessarily a bad test runner, but I like having options.


On Wed, Dec 12, 2012 at 9:38 AM, Pierre-Yves Gérardy <pyg...@gmail.com> wrote:
Here's my setup:

In app.js, I create a separate app for the tests:

Here's what I ended up with:

client/
├── code
│   ├── app
│   │   ├── controllers.js
│   │   ├── directives.js
│   │   ├── entry.js
│   │   ├── filters.js
│   │   └── services.js
│   └── libs
│       ├── 00.jquery-1.8.3.js
│       ├── 05.angular-1.0.2.js
│       ├── 10.bootstrap-2.2.2.js

client/code/app/entry.js

require('/controllers');

client/code/app/constrollers.js

var app = angular.module('app', ['app.filters', 'app.services', 'app.directives']);

app.controller('FooCtrl', ['$scope', function() { }]);

filters.js

angular.module('app.filters', [])
  .filter('FooFilter', function () { } );

services.js and directives.js are similar - in their own angular module that is injected as a dependency into the main 'app' module.  This way the only 'require' necessary is in entry.js which I don't care about testing anyway.  The rest is just vanilla JS.  SS will browserify these when they're served but I don't care about that -- I can test them standalone outside of SS altogether

├── test
│   ├── client
│   │   ├── e2e
│   │   ├── lib
│   │   │   ├── angular
│   │   │   │   ├── angular-1.1.1.js
│   │   │   │   ├── angular-mocks.js
│   │   │   │   └── angular-scenario.js
│   │   │   ├── chai.js
│   │   │   └── sinon-1.5.2.js
│   │   ├── mochaConfig.js
│   │   └── unit
│   │       ├── controllerSpec.js
│   │       └── filterSpec.js
│   ├── mocha.opts
│   └── server
│       ├── forward-test.js
│       └── mocha.opts

This is split test/client and test/server for testing.  To run server side tests, I have a Makefile that calls mocha directly.  For client side, I follow the same thing they have in https://github.com/angular/angular-seed 

Testacular configured to use MOCHA + growl

/config/testacular.conf.js -- mochaConfig.js has a single line like yours: mocha.setup('bdd')

basePath = '../';

files = [
  MOCHA,
  MOCHA_ADAPTER,

  // !! chaijs client side assertions
  'test/client/lib/chai.js',
  'test/client/lib/sinon-1.5.2.js',
  'test/client/mochaConfig.js',

  // !! libs you want included as <script> tags; order here is important
  'client/code/libs/*jquery*.js',

  // !! see comment in exclude array below
  'test/client/lib/angular/angular-1.1.1.js',
  'client/code/libs/*bootstrap*.js',

  // !! code i want to test
  'client/code/app/*.js',

  // !! provides angular mock / inject
  'test/client/lib/angular/angular-mocks.js',

  // !! test sources
  'test/client/unit/**/*.js'
];

exclude = [
  // !! has requirejs in it, won't work
  'client/code/app/entry.js',

  // !! we have to use 1.0.2 angular b/c of https://github.com/angular/angular.js/issues/1674
  // but only in 1.1.1 did they add support for angular.mock.module, angular.mock.inject
  // for mocha
  'client/code/libs/*angular.js'
];

autoWatch = true;

colors = true;

browsers = ['Chrome'];

reporters = ['growl', 'dots'];

Test cases work as standard test cases now, nothing goofy about them:

/test/client/unit/controllerSpec.js

describe('Controller Tests => ', function () {

  var $controller, $rootScope, /* any other angular services / objects you need */
    assert = chai.assert,
    expect = chai.expect,
    should = chai.should();

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

  beforeEach(angular.mock.inject(function ($injector) {
    $rootScope  = $injector.get('$rootScope');
    $controller = $injector.get('$controller');
  }));

  describe('FooCtrl => ', function () {
    var params, ctrl, scope;

    beforeEach(function () {
      scope = $rootScope.$new();
      params = { $scope: scope /* add any other mock services you need to inject here */ };
      ctrl = $controller('FooCtrl', params);
    });

    it('should test something on FooCtrl', function () {
       // do something to controller
    });

  });
});

Run it via /scripts/tests.sh (same script as in angular-seed), and it supports live-reload any time I change the code or test code...

Plus: I learned something cool, you can add the line:

debugger

anywhere in your code or test code, and for browser tests, you'll hit the breakpoint if you need to debug something.  i use chrome, so usually i have to open the JS console in chrome after testacular starts a new browser window, and then CMD+S on a file to trigger a re-run

--

Davis Ford

unread,
Dec 12, 2012, 10:50:45 AM12/12/12
to socket...@googlegroups.com
I was just thinking about this and I like it.  You've got a simple view:

The view is pretty simple. Specifically:
[classic SS header.]
<body>
   <div id="mocha"></div>
</body>

...but you can add a section that has a button to click, send rpc to server, and execute the server-side tests, send back the results -- you can have both client / server side tests in browser.  You could host this out on a CI machine, have it git pull on any change -- with nodemon running the app, it will re-load browser, and re-trigger all the tests.  Plus, you can let people run your client tests anytime they want by hitting a separate url http://host/test -- the only downside is you don't want to serve the test in production, but I suppose you can have conditional logic that wraps that route if SS_ENV=production

if (ss.env !== 'production') {  app.route('/test', function (req, res) { res.serveClient('test'); }

On Wed, Dec 12, 2012 at 10:38 AM, Davis Ford <da...@daisyworks.com> wrote:
Pierre, thanks for the writeup -- that is an interesting way to do it -- I hadn't thought about making a separate ss.client.define('tests') ....do you still obtain live reload of the tests when code and/or test code changes?  That was my single biggest driving factor for what I wanted to achieve.  It seems like you would if you run it with nodemon and serve it as a standard webapp.  This would allow you to de-couple from the shackles of testacular...not that it is necessarily a bad test runner, but I like having options.



Pierre-Yves Gérardy

unread,
Dec 12, 2012, 11:02:58 AM12/12/12
to socket...@googlegroups.com
On Wednesday, December 12, 2012 4:38:06 PM UTC+1, Davis Ford wrote:
Pierre, thanks for the writeup -- that is an interesting way to do it -- I hadn't thought about making a separate ss.client.define('tests') ....do you still obtain live reload of the tests when code and/or test code changes?  That was my single biggest driving factor for what I wanted to achieve.  It seems like you would if you run it with nodemon and serve it as a standard webapp.  This would allow you to de-couple from the shackles of testacular...not that it is necessarily a bad test runner, but I like having options.

Yup, you keep the test app open in a separate window, and the tests are run when you modify amy file in the client directory, be it your main app or the tests.

To be sure I made it clear, all code is in the same folder:

ssApp
+- client
   +- code
      +- app ...
      +- tests ...
      +- libs ... (the common ones).
 
It would probably be better to put the server tests elsewhere.


On Wednesday, December 12, 2012 4:50:45 PM UTC+1, Davis Ford wrote:
I was just thinking about this and I like it.  You've got a simple view:

The view is pretty simple. Specifically:
[classic SS header.]
<body>
   <div id="mocha"></div>
</body>

...but you can add a section that has a button to click, send rpc to server, and execute the server-side tests, send back the results -- you can have both client / server side tests in browser.  You could host this out on a CI machine, have it git pull on any change -- with nodemon running the app, it will re-load browser, and re-trigger all the tests.  Plus, you can let people run your client tests anytime they want by hitting a separate url http://host/test -- the only downside is you don't want to serve the test in production, but I suppose you can have conditional logic that wraps that route if SS_ENV=production

if (ss.env !== 'production') {  app.route('/test', function (req, res) { res.serveClient('test'); }

Yup, sounds interesting. I think you can load socket responders conditionally.

-- Pierre-Yves

Davis Ford

unread,
Dec 12, 2012, 12:58:27 PM12/12/12
to socket...@googlegroups.com
I just migrated my setup over to this -- it is pretty brilliant -- thanks a lot for sharing it.  Another plus is that you can now test in any browser -- whereas with testacular, you were limited to the browsers it supports, and you had to specify which browser which it launched for you with its own websocket pipe.

This is just so much cleaner + simpler -- plus you get mocha results in the browser, whereas with testacular, you get no info about test results...only on the console. 


On Wed, Dec 12, 2012 at 11:02 AM, Pierre-Yves Gérardy <pyg...@gmail.com> wrote:
Yup, you keep the test app open in a separate window, and the tests are run when you modify amy file in the client directory, be it your main app or the tests.

Pierre-Yves Gérardy

unread,
Dec 12, 2012, 3:49:44 PM12/12/12
to socket...@googlegroups.com
:-)

Pierre-Yves Gérardy

unread,
Dec 12, 2012, 3:58:04 PM12/12/12
to socket...@googlegroups.com
Note that the module global provided by angular-mocks is overshadowed by the module local (of module.exports). 

So:
var mockModule = window.module;

... and you're good to go.

Davis Ford

unread,
Dec 12, 2012, 9:39:45 PM12/12/12
to socket...@googlegroups.com
In my test, I was using the full namespace of angular.mock.module and it all works fine.  I also didn't tweak angular-mocks.js isSpecRunning like you cited above...still tests passing / running / working -- and app still works as expected.


On Wed, Dec 12, 2012 at 3:58 PM, Pierre-Yves Gérardy <pyg...@gmail.com> wrote:
Note that the module global provided by angular-mocks is overshadowed by the module local (of module.exports). 

So:
var mockModule = window.module;

... and you're good to go.

--

Yi Wang

unread,
Dec 23, 2012, 10:37:54 PM12/23/12
to socket...@googlegroups.com
"using the full namespace of angular.mock.module" works for me as well

updated https://github.com/yiwang/angular-phonecat-livescript-socketstream to support mocha in browser as Pierre-Yves pointed out, also use http://metaskills.net/mocha-phantomjs/ to support run tests command line, only change is

      if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
      else { mocha.run(); }
in entry.js for test-client

Yi Wang

unread,
Dec 27, 2012, 2:21:45 AM12/27/12
to socket...@googlegroups.com
update: added coverage report with node-jscoverage and mocha-phantomjs

Davis Ford

unread,
Dec 28, 2012, 3:48:43 PM12/28/12
to socket...@googlegroups.com
Nicely done.  I'm a big fan of phantomjs, also casperjs... I used them to great effect when I had to do a bunch of web scraping.  Phantom is a perfect fit here -- especially for headless CI servers.  The only downside is debugging is a drag with phantom...which is why it is always nice to still be able to run in the browser for client side tests with something like mochajs
--
Reply all
Reply to author
Forward
0 new messages