Autotesting Javascript in Rails
I used to love Javascript so much that it would scare me. Why? Because I used to never write any tests. No unit tests. No integration tests (e.g. Selenium). Why? I didn’t know how. Not properly. I didn’t know how to write Javascript unit tests with an autotesting tool, like ZenTest’s autotest command.
And now…
I am now free to write as much Javascript as I like knowing forever I’ll write Javascript tests first, and Javascript in libraries second. TDD for Javascript beckons!
Below is a “getting started” tutorial, a helpful autotesting plugin, and hints about a TextMate bundle for Javascript Unit Testing and a future Peepcode on Javascript Unit Testing.
Installation
Fundamentally, the ideas here are app server-neutral. We’re using the unittest.js library from scriptaculous. But here I’ll show/demo the Rails plugins that make this all uber easy to get started.
Firstly, create a blank rails app, or use an existing app. The tutorial should be non-invasive - just a few “demo” files to delete when you’re done. This tutorial is independent of your app.
ruby script/plugin install javascript_test mkdir test/javascript ln -s ../../vendor/plugins/javascript_test/assets/ test/javascript/assets
For Windows people copy vendor/plugins/javascript_test/assets into test/javascript/assets, as you don’t have symbolic links, which is sad.
At this stage, you can add and commit these files to your project. The rest of this article is destructible demo files and tests.
Creating javascript tests
Let’s say
$ ruby script/generate javascript_test maths
exists test/javascript
exists public/javascripts
create test/javascript/maths_test.html
create public/javascripts/maths.js
maths.js is just a blank javascript file. The important file is maths_test.html. Its packed with lots of “getting started” goodies.
Open test/javascript/maths_test.html and find the testTruth method.
testTruth: function() { with(this) {
assert(true);
}}
This is an example of a javascript test method, with a sample assert call. It has the same api as the Ruby test::unit assert call. Thomas Fuchs’ presentation Adventures in JavaScript testing gives a good rundown of the available assert methods (and the BDD style syntax that is available). I’m also preparing a TextMate bundle to generate the assert calls, with the same “as”, “ase”, “asnn” tab completions as the Ruby bundle. It makes me happy when using it.
Running unit tests
The most holistic way to run your javascript unittests, is against all the browsers you have on your local machine. The javascript_test plugin comes with a rake task for this: test:javascripts
$ rake test:javascripts (in /Users/nicwilliams/Documents/rails_apps/imindi_theme) /test/javascript/maths_test.html on Safari: SUCCESS /test/javascript/maths_test.html of Firefox: SUCCESS Skipping Internet Explorer, not supported on this OS Skipping Konqueror, not supported on this OS
In the browsers you’ll see:
It runs all your tests on all browsers. That’s great for a Continuous Integration process, but for me, coming from the ZenTest world of autotest, I only want to run those tests for which something has changed.
I want continual feedback. Small change, run small test. autotest gives me that for Ruby, but rake test:javascript doesn’t.
Autotesting javascript tests
On my Mac, I’ve set up something as a starting point for autotesting javascript tests, based on the “small change, run small test” principle.
$ ruby script/plugin install http://drnicutilities.rubyforge.org/svn/plugins/javascript_test_autotest/ . . Edit config/javascript_test_autotest.yml for the browser(s) to use for autotesting.
Open config/javascript_test_autotest.yml and uncomment the browser(s) you want to autotest with. I autotest on Safari on the Mac, even though I develop/debug on Firefox, because it reloads each page in the same tab, which I like, and I’m having problems getting this to work into Firefox at all.
My yml file looks like:
browsers: safari: '/Applications/Safari.app/Contents/MacOS/Safari'
So, the current version is known to work for Mac OS X and Safari. If you get this plugin working on different O/S and browsers, let me know below or please submit a patch to http://groups.google.com/group/drnicutilities
From a command line:
$ script/js_autotest Watching public/javascripts/controls.js, public/javascripts/application.js, test/javascript/maths_test.html, public/javascripts/effects.js, public/javascripts/dragdrop.js, public/javascripts/prototype.js Files: 6
Let’s TDD with js_autotest
In test/javascript/maths_test.html, replace testTruth, with the following and save:
// Maths class should exist
testMathsClassShouldExist: function() { with(this) {
assert(Maths, 'Where is Maths?');
}},
js_autotest will automatically load maths_test.html into your browser, and its tests executed.
Now, let’s fix the problem by creating the Maths javascript class. In public/javascripts/maths.js, add:
var Maths = Class.create();
Maths.prototype = {
initialize: function() {
}
}
js_autotest will again automatically reload maths_test.html into your browser, and its tests executed.
Yay for TDD and Yay for “small change, run small test”.
Peepcode
I love Geoffrey’s Peepcodes. Geoffrey has all my money in Peepcode credits. More importantly, Geoffrey’s due to give birth this year to #1 child. Geoffrey, who works from home, is blissfully unaware that soon he’ll be a full-time father and part-time Peepcode author. This could mean fewer peepcodes on important peepcode-worthy topics.
I always wished there was a Peepcode on Javascript Unit Testing. It would take many blog posts to cover as much as can be covered in a single 60 minutes peepcode. So, we’re writing/videoing one.
As a Javascript developer [has anyone ever heard of a Javascript Users Group?] let me know what specifically you’d like covered in this video, so I don’t accidently miss something.
[optional] Install some patches
There was a section here about some patches that could be applied. These have now been applied, and javascript_test plugin has been updated with prototype 1.6.0.1. Life is good.
Find objects in IRB directly from browser URLs
A long time ago, I tired of going into the irb/console and finding objects/models using the traditional ActiveRecord command Person.find(15) and now I’m sitting pretty: I can paste in URLs to fetch objects.
# No more of this: => Person.find(15) # instead: => people/15
people/15 is something you’ll copy+paste directly from your browser: http://localhost:3000/people/15
Of course, the url is based on your routing + controllers, so the assumption here is that your routes/controllers map to your active record models. That is, your app is smothered in RESTful love and cuddles.
Not following this? Here’s a video:
How to make this work at home
Copy and paste the following into your .irbrc file:
Thanks goes to…
The some original code for this comes via Mike Clark, who had the idea for syntax activity(6). This was good.
I previously had another idea to support the syntax 6.to_activity using the RubyGem to_activerecord. I still like the id.to_class_name structure of this and still use it.
But if I have a perfectly nice looking url sitting in front of me, I can now paste the class_name/id part into irb and I’m off and running.
Happy New Year.
Installing Mingle with Capistrano/Deprec
The other day I visited the ThoughtWorks office in Melbourne (they hosted the Melb Ruby meeting). The most notable feature of their office versus any office I’ve ever worked in before were the 5×3 cards (and post-it notes) … all over the walls.
From my understanding - which came after asking lots of questions - is that the cards help with:
- The team (developers, clients, etc) deciding what parts of the application are most important to be built. The cards are isolated stories/use cases and should implicitly/explicitly state the business value. At each stage of the project - “iterations” which are 1 or 2 week long - the team decides which cards/stories should be implemented.
- The cards for an iteration are placed on the wall. Thus it is transparent to all the team what work is being performed.
- From left to right, the wall is divided into columns. Each card can be placed in one column. Columns are typically labelled with stage names like “Under development”, “Completed”, “QA/Testing”, “Accepted”. This gives a visualisation of what work/card is where in the development/release process. It can also indicate if there is a bottleneck - a lot of cards in the “QA/Testing” column might mean you need more testers.
The first point - using stories on cards to decide what should be built now - seemed like a winning idea. A new client of mine has trouble translating a gazillion feature requests into a sequence of “this part should be built now” stages. Everything is uber cricital, all the time. And often the feature requests don’t seem related to something a user might actually want to use.
Whilst cards on a wall would work if the client and developers were co-located (aka “sitting together”), that’s not the situation here. I’m in Brisbane, and they are in Sydney (1000km).
With colourful 5×3 cards and the promise of fruitful client relationships burned into my retina I wondered what web tools might help with emulating this process. The cards. The wall. The transparency. The understanding that “software costs money to build so what is most important?”
And I remembered Mingle - a tool built by ThoughtWorks for agile project development, but not (to my knowledge) used by any ThoughtWorks teams themselves. UPDATE: Jay Fields says several teams are currently using it.
But I can understand why they don’t use it. They have their own 5×3 cards with their “ThoughtWorks” logo on it. Well, they had some. I stole a bunch. They also have walls and co-location.
Nonetheless, I gave it a burl. I runs nicely when the server is installed on my CoreDuo 2gig MacBook. It didn’t run nicely at all on my 256Mb VPS that was already busy running a bunch of other things (mongrels, mysql, etc).
Mingle requires 2 gig of RAM. Even on the ever affordable Slicehost, that’s $140 USD a month. Mingle might be free for < 5 users (and OSS projects) but there is a notable hardware requirement, thanks I guess to the Java/JRuby/Rails stack.
But I've done something useful - I've written capistrano/deprec recipies to install Mingle on a remote machine with (minimal) manual intervention. You just need to choose a machine with 2G RAM spare. You also need Java and MySQL installed, but there are capistrano tasks to help with that too.
Ahh, the world of remote automation.
The recipe below is mostly *nix agnostic, except the java_install task which uses ubuntu/debian’s apt-get system. Actually, it tells you what to manually run yourself, as Sun are tools and require you to “click” the word “Yes” twice. I don’t know how to automate this with Capistrano.
The mingle_install task downloads and installs Mingle, and includes an JVM optimisation option that I found on the forums (the MingleServer.vmoptions file created).
So, from scratch, if you want to install Mingle on a remote server (independently of any Rails app etc), do the following:
sudo gem install deprec -v 1.9.1 alias cap1='cap _1.4.1_' mkdir -p mingle_installer/config cd !$ vim deploy.rb
And insert the following into the deploy.rb script.
Modify YOURDOMAIN to an IP that you have root access to. The script will create two folders - /usr/local/src/mingle and /var/www/apps/mingle. Its pretty harmless.
To install, run the capistrano tasks:
cd .. cap1 java_install cap1 mingle_install cap1 mingle_start
BUT, mingle_start doesn’t work. The server just doesn’t start and I don’t know why.
So, ssh into your machine (you had to do this for the java install step too BTW), and run:
/usr/local/src/mingle/MingleServer start
If we can fix this then that’d be uber cool.
Going offline without your favourite Subversion repository?
UPDATE: useful gitify command below.
All my client projects are hosted on Subversion repos. All my OSS projects are hosted on Rubyforge Subversion repos.
And tomorrow I head off for the 2nd “no internet” RailsCamp megafest, being held outside of Melbourne. From what I heard about the 1st one, it involved a lot of Guitar Hero II and beer. This weekend I’m promised that someone is bringing a Wii - dear God I do love the tennis game.
Like MacDonalds burgers and Veganism, if there are two things that don’t go together its Subversion and “No Internet”.
Fortunately, for all RailsCampers, there is a solution to your fears that you won’t be able to hack on your favourite project, and will be forced into playing Guitar Hero II/III and/or the Wii for the whole weekend.
Git.
Specifically, the git-svn command.
More specifically, follow the following steps:
git-svn clone <your svn url> projectname.git- Go camping for the weekend.
- Commit changes with
git commit -a -m "hahaha I'm camping and working - doh!" - Come home from camping.
git-svn rebaseto re-import any SVN changes that might have been committed whilst you were away. If there are conflicts, just follow the instructions.git-svn dcommitand your svn repository will be updated with all your fancy changes; no one will ever know you used Git.
Installing Git
I remember having problems getting git-svn to run because the default path to the perl cmd is crap, and you need to specify PERL_PATH='/usr/bin/env perl', I think, before installation.
For MacPorts etc, its sudo port install git-core. I think the reason its not just called ‘git’ is to remind you that you know nothing about git. Which is fair. You don’t if you don’t know how to install it, I guess.
From source, get the tarball from the home page.
For Windows, use cygwin.
Gitify command
Want a utility cmd to create a Git repo in the parent folder of a Subversion project? That is, go to your Rails app that is checked out from a Subversion repo, and run gitify, and you will have a foldername.git folder in the parent folder.
Stick this code into ~/bin/gitify and add ~/bin to your path:
Merb 0.4 - Installing Edge Merb and using new Generators
Merb 0.4 will include generators (via RubiGen) to make it uber-easy for developers to create Merb apps. Here’s a HOWTO screencast (lifted from rubyconf talk) on getting the required gems from SVN, and setting up a Merb to use ActiveRecord.
The video also shows how your generated model/controllers can either have test::unit OR rspec test files generated.
As Albert Einstein said “All your base belong to us.”
Installing Edge Merb and using new Generators from probablycorey on Vimeo.
Alternately, download the 800×600 QuickTime version (22Mb).
Errata:
- To change between rspec and test::unit (or between different ORMs), use
Merb::GENERATOR_SCOPEfound in config/merb_init.rb, rather than hack script/generate as I do in the video
- Prior to following the video tutorial, you need to checkout the two edge gem sources:
svn co http://svn.devjavu.com/merb/trunk merb svn co http://svn.devjavu.com/merb/plugins/merb_activerecord
The videos don’t have voice overs, because if I’d done that then I’d have been completely bored when I gave my talk. As it was, I think half my talk was videos and screencasts - I felt like a TV host… it was odd. But, you’ll get the gist.
For more step-by-step instructions, see the man who helped write the Merb generators - Daniel Neighman (irc - hassox).










