SiKaNrOnG.com

Agile Developer Extraordinaire

Blog

Rails Javascript Testing (unittest.js the right way)

September - 02 - 2008

Hey all! It's been awhile since I've written. I hope you guys find this helpful, because I couldn't find this anywhere else on the internet and it took me ages. So, I've been using the javascript_test plugin. You can install this plugin with the following command in your project root directory:

ruby script/plugin install javascript_test
mkdir test/javascript
ln -s ../../vendor/plugins/javascript_test/assets/ test/javascript/assets

Now, what you'll find is by default all this does is set up a framework with which you can test your javascript libraries againt NEW markup, that YOU have to write. I found this highly unrealistic. Only VERY rarely is the javascript in a rails application going to be able to be accurately tested in a 'vaccuum', meaning that the DOM and the JS have everything to do with eachother. Also, you want to be able to test the effects of your rjs templates, most of which will have everything to do with your markup and your application server running. So, I thought about this for a while, and came up with (what I think) is a pretty elegant solution.

When you run your tests (which you can do with the following command):

rake test:javascripts

What happens is the javascript_test plugin initializes a new WEBrick server running on port 4177, and all it really does is provide some basic functionality to run the JS tests with your newly written markup. It has nothing to do with your application ruby code, nor can you access that code via AJAX requests. Crap, huh? So, clearly this needs to be improved upon, and I believe I've found a way. Basically what I'm doing is opening a new window with my application server running in it, and simply running the tests (and the assertions) using the dom and JS included by the app! Completely unobtrusive, and you get the full power of the application when you test the JS. However...

Issue: Same Origin Policy

Basically the first thing you're going to get screwed by is this, in all modern browsers, JS from within a parent window cannot access the DOM of the child window in any significant way. This is to prevent cross side scripting attacks, which would otherwise hijack your browser behind the scenes and post midget porn all over your facebook. Now, you wouldn't think it - but according to same-origin policy 'http://localhost:3000' and 'http://localhost:4711' are actually different domains (because of the port). Firefox 3 (and previous) don't even provide a way to temporarily disable this, so you have to exploit the single loophole in same-origin policy: subdomains. You're allowed to access the child window if it belongs to a subdomain of the parent window. So,

"How do I turn localhost:3000 and localhost:4711 into jstest.mysite.test and mysite.test??"

Solution: Subverting Same Origin Policy via Apache

That's right! It is possible to use apache (with mod_proxy) and /etc/hosts to trick your browser into using the same origin subdomain loophole. I'm using apache 1.3 for this (Default on OSX Tiger), but it won't change too much for Leopard, or other versions of Apache in general.

Step 1) Uncomment these lines in the file /etc/httpd/httpd.conf (default OSX tiger file location)

#AddModule mod_proxy.c
#LoadModule proxy_module       libexec/httpd/libproxy.so

Step 2) Add these sections to httpd.conf (replace mysite with whatever you want to use)

<VirtualHost *:80>
    ServerName localhost
</VirtualHost>

<VirtualHost *:80>
   ServerName mysite.test

   ProxyPass / http://localhost:3000/
   ProxyPassReverse / http://localhost:3000
</VirtualHost>

<VirtualHost *:80>
   ServerName js_test.mysite.test

   ProxyPass / http://localhost:4711/
   ProxyPassReverse / http://localhost:4711
</VirtualHost>

Step 3) Add these two lines to /etc/hosts file

127.0.0.1    mysite.test
127.0.0.1    js_test.
mysite.test

Step 4) restart Apache:

sudo apachectl restart

Step 5) Edit the file vendor/plugins/javascript_test/lib/javascript_test.rb, change line 176 to

browser.visit("http://js_test.mysite.test#{test}?resultsURL=http://js_test.mysite.test/results&t=" + ("%.6f" % Time.now.to_f))

Step 6) Make sure you add this to your application layouts AND to the <head> tags of the files in test/javascript (NOTE: DO NOT LET THIS BIT OF CODE ONTO YOUR PRODUCTION SERVER). I've created a mechanism that I can turn on and off in environment.rb, a better solution would be to use arguements passed to script/server on rails start.

<script type="text/javascript">
    document.domain = "mysite.test";
</script> 

Step 7) Write tests that use your server! This is an example from one of mine - it opens the application in a separate window and logs in before doing other tests.

var mysite_window = null;
  new Test.Unit.Runner({
   
    setup: function() {
      mysite_window = window.open("http://mysite.test/login", "mysite_window");
      var execute_string = ""+<r><![CDATA[
        var inputs =
mysite_window.document.forms[0].getElementsByTagName('input');
        inputs[0].value = "user@email.com";
        inputs[1].value = "password";
       
mysite_window.document.forms[0].submit();
      ]]></r>;
      setTimeout(execute_string, 2000);
     
    },
   
    teardown: function() {
     
    },
   
    testMysiteLogin: function() { with(this) {
      wait(10000, function(){
        assertEqual(
mysite_window.location, "http://mysite.test/member-overview");
      });
    }}
     
  }, {testLog: "testlog"});

Note that you don't have to have the window.open in the setup method, and in fact in most cases it's better if it just executes before the runner does (because it only needs to do so once). Anyway, You get the idea, now you can access all your JS methods from the window handler returned from window.open. So if I had a 'popup_cheesecake' function in my application JS, all I would have to do to test is is write something like:

    testCheesecake: function() { with(this){
       //This Allows us to get the window handler of the popup without re-loading the URL
       var mysite_window = window.open("", "mysite_window");

       assertEqual(
mysite_window.popup_cheesecake(), true);
    }}

...And that's it! So anyway - I hope this has helped some people!! I know people on the rails scene have been looking into JS testing and seen solutions like the javascript_test plguin and shuddered in horror. This solution helps actually integrate the rails application AND the javascript testing suite, which in turn helps bring testing closer to what it's actually supposed to do - testing the software under real conditions!



(No comments yet.. I COMMAND YOU TO COMMENT!)


Leave a Comment:

Name:
Email (not displayed):
Website (optional):
Captcha:
simple_captcha.jpg
(type the code from the image)
Comment:
Back To Entry List