Automating AngularJS With Yeoman, Grunt & Bower
Written by Simon Bailey
In this post I will show you how you can use an amazing set of tools and libraries to help speed up development of AngularJS applications. We will use Yeoman to generate the project/files for us and Grunt to automate tasks such as running the server/tests. Bower is a package manager for the web and please be aware I don’t go so much into Bower as the other tools take up a lot of the post already, just know what it does and that it’s pretty cool. Please keep in mind that all these tools do so much more so read their documentation and get a full grasp of their capabilities.
Update - 24/11/2013
I have had some emails regarding an issue with running grunt karma
displaying the following error:
Warning: Task "karma" not found. Use --force to continue.
To solve this please ensure you have installed the Grunt plugin for Karma by running:
npm install grunt-karma --save-dev
Then load the task by adding grunt.loadNpmTasks('grunt-karma');
to your Gruntfile.
There seems to be an inconsistency in certain project setups, however, if you find you’re getting the above error the solution typically seems to work.
Create the project
As in my previous blog post on How to Test an AngularJS Directive you will need to install the core libraries needed for this application:
sudo npm install -g yo grunt-cli bower karma
I have included Bower and Karma as they are used with the application generated by Yeoman.
Yeoman is not specific to AngularJS so we need to install generator-angular.
sudo npm install -g generator-angular
Create the project directory and run the Yeoman angular project generator.
mkdir angularjs-grunt-example && cd $_
yo angular
Accept all the defaults for this example project:
Would you like to include Twitter Bootstrap? (Y/n)
If so, would you like to use Twitter Bootstrap for Compass (as opposed to vanilla CSS)? (Y/n)
Would you like to include angular-resource.js? (Y/n)
Would you like to include angular-cookies.js? (Y/n)
Would you like to include angular-sanitize.js? (Y/n)
The package.json file
Yeoman will generate a package.json file (similar to a Gemfile used in Ruby) which will list all project dependencies. To install these you can simply run npm install
.
Run grunt karma
grunt karma
All tests pass!
Chrome 27.0 (Mac): Executed 1 of 1 SUCCESS (0.085 secs / 0.018 secs)
But wait….there is a warning:
WARN [watcher]: Pattern "/Users/newtriks/src/newtriks-dev/js/angularjs-grunt-example/test/mock/**/*.js" does not match any file.
Let’s fix this. I tend to prefer to add directories as required and for now I have no mocks so lets comment out the include. Open karma.conf.js and comment out the line with 'test/mock/**/*.js'
.
Run grunt karma
again and the warning should be gone.
Note: If you want to follow the same logic for karma-e2e.conf.js also this will ensure the warning is also fixed for the end-to-end tests.
Setup PhantomJS
I prefer not to have a browser window opening for run my tests and can recommend PhantomJS. Follow the instructions on the website to download and install PhantomJS.
Open karma.conf.js and find the line that has browsers = ['Chrome'];
(approx line:48) and replace Chrome with PhantomJS.
Run grunt karma
again and you should notice that the browser window no longer opens.
Note: If you want to follow the same logic for karma-e2e.conf.js also this will ensure PhantomJS is also used for the end-to-end tests.
Run grunt server
grunt server
Ey up, what’s this:
Running "livereload-start" task
... Starting Livereload server on 35729 ...
... Uhoh. Got error listen EADDRINUSE ...
This warning is related to LiveReload which monitors files for changes and reloads the browser. This warning therefore may not show unless you have the app installed, or alternatively have the SublimeText LiveReload plugin. This is a reported issue and the solution is to change the livereload port in the Gruntfile.
Look for the line yeoman: yeomanConfig,
(approx line:22) and add the following directly below it:
livereload: {
port: 35728
},
Run grunt server
again and all should run as expected with the end result being the app running in the browser.
Create e2e & unit test tasks
A Gruntfile is comprised of the following parts:
* The “wrapper” function
* Project and task configuration
* Loading grunt plugins and tasks
* Custom tasks
The main aspects we will be tinkering with in the Gruntfile are tasks. The aim is to be able to easily run either the end-to-end tests (e2e) or all of the unit tests.
To achieve this we need to create and register task targets; one named e2e and one named unit.
Create/edit tasks
Within the Gruntfile find the karma task that looks like the following (approx line:104):
karma: {
unit: {
configFile: 'karma.conf.js',
singleRun: true
}
}
The karma task currently has one target named unit. Add another target to the karma task named e2e ensuring the configFile points to the karma-e2e.conf.js created by Yeoman.
karma: {
e2e: {
configFile: 'karma-e2e.conf.js',
singleRun: true
},
unit: {
configFile: 'karma.conf.js',
singleRun: true
}
}
Register tasks
Within the Gruntfile find where the test task is being registered (approx line:282):
grunt.registerTask('test', [
'clean:server',
'coffee',
'compass',
'connect:test',
'karma'
]);
Remove this test register task and register our e2e and unit test tasks:
grunt.registerTask('test:unit', [
'clean:server',
'coffee',
'compass',
'connect:test',
'karma:unit'
]);
grunt.registerTask('test:e2e', [
'clean:server',
'coffee',
'compass',
'livereload-start',
'connect:livereload',
'karma:e2e'
]);
Running the tasks
Now to simply run the unit tests use grunt test:unit
and for the e2e tests grunt test:e2e
, easy!
End to end test
Note: I could swear that Yeoman used to auto generate test/e2e/scenarios.js but running it for this example I see no sign?
Create a directory named e2e in the test directory and create a file named scenarios.js and add the following:
'use strict';
describe('angularjsGruntExampleApp app', function() {
beforeEach(function() {
browser().navigateTo('/');
});
describe('Homepage', function() {
it('should display the correct route', function() {
expect(browser().location().path()).toBe('/');
});
});
});
End to end test running
If you are running your application using grunt server
and then in a separate terminal tab decide to run your e2e tests grunt test:e2e
then you will see the following error:
Running "livereload-start" task
... Starting Livereload server on 35728 ...
... Uhoh. Got error listen EADDRINUSE ...
You may recognise this if you came across the livereload issue I mentioned above. The reason it’s re-emerged is due to the e2e task also using livereload (issue). See for yourself by looking at the registration of the e2e test task in the Gruntfile:
grunt.registerTask('test:e2e', [
'clean:server',
'coffee',
'compass',
'livereload-start',
'connect:livereload',
'karma:e2e'
]);
It’s my understanding (and please comment if you have alternative explanations) that in the above scenario we are running the application using two separate servers. Both instances however, will try and share the existing livereload port. I see two options to work around this but both need you to add a proxy to the karma-e2e.conf.js and assign a different port:
proxies = {
'/': 'http://localhost:9000' // Keep this in sync with localhost port in Gruntfile.
};
urlRoot = '/__e2e/';
Take note of the port and ensure it’s in sync with the localhost port value in the Gruntfile:
connect: {
options: {
port: 9000,
hostname: 'localhost'
}
Piggy back option
Point the e2e test at a running application server.
Remove the livereload-start
and connect:livereload
tasks from the e2e task in the Gruntfile:
grunt.registerTask('test:e2e', [
'clean:server',
'coffee',
'compass',
'karma:e2e'
]);
Single server option
Ensure that the application server is not running when you run the e2e task.
Single server option makes more sense to me as I don’t see the need to have the application running to run the e2e tests.
Karma autoWatch
One of the great feature of karma is auto watch. What this means is that if you set autoWatch to true then all the tests will get run when a file changes (this excludes changes to the karma configuration files).
Within the karma.conf.js file (approx line:38) find autoWatch = false;
and change it to true. Based on us running the test:unit task, confirm in the Gruntfile karma task that singleRun = false;
is changed to true.
Now if you run grunt test:unit
you will find that it leaves the test server running and any changes to project files immediately run the tests again.
Note: Once again server/port issues arise if you try running this task alongside the grunt server
task. I am yet to find a solution to this one. Typically I tend to:
- Set
singleRun = true;
in the karma task (Gruntfile). - Set
singleRun = false;
in the karma.conf.js file and usekarma start
as opposed togrunt test:unit
.
Task running summary
Due to the identified conflicts with ports and servers here are the typical scenarios and the corresponding tasks I typically run.
- Running the application
grunt server
- Running the e2e tests
grunt test:e2e
- Running the unit tests
grunt test:unit
- Running the application and unit tests simultaneously (seperate Terminal windows)
grunt server
&karma start
.
All the source to this project can be found on Github.
Useful links
- @amscotti’s Yeoman posts: My Friend Yeoman & Yeoman working with APIs.