Friday, February 4, 2011

Testing your Web Application with QUnit and JQuery 1.5

QUnit is a great framework for testing your JavaScript. With JQuery 1.5 it becomes more powerful. In this blog I'm going to introduce new technic that allows me to test quite complex business logic in my web application in the most clear, concise and readable way.

So, for example, there is a page with some directory index and the 'Create Folder' button. On click it shows a dialog where you have to type a folder name and press the 'create' button. It will send a query to a server that will create new folder on a file system, refresh the directory index and send it back to the client. I agree that it is not so complex scenario. But, how is it possible to test what javascript in your browser creates and sends right query to the server, what the server creates the folder and refreshes the directory index and what the browser again shows this refreshed directory index for you by one run?

I'm sure that devotees of the unit testing will say that it's wrong approach and I should split my test in three parts and test them as modules in isolation. But stop, it's not my way. I want to write acceptance test and be sure that this integral behavior works.

So, of course you can use selenium along with cucumber or fitnesse or any other acceptance testing framework. And earlier I would do it in the same way. But now I know the better approach.

Here is a loader, that will load and execute my test. It has default html layout to be used along with QUnit. Also it includes auxiliary scripts from testLoader.js and of course createFolderTest.js with my test.

A key element here is iframe. I use it to load my test page there. It helps me to divide and incapsulate my test code and production code what allows to manipulate my testing page without reloading my test. BTW, a big thanks to Mike Plavskiy, boss and colleague of mine. It is his idea to use iframe instead of selenium.

Next is a couple of the test loader utility methods and test itself.


The asyncTest call is provided by QUnit and dedicated to test asynchronous code. Such record means that execution of the QUnit code that is in charge of verifying test results will be stopped until the start function is called. You can read more about it in this article. At the same time code inside the asyncTest block is running. First string here is new the Deferred Objects feature of JQuery 1.5. The $.when( frame.go(url) ).then(function(_$)... construction means that as soon as the frame.go method, that loads test page into the frame, is completed a function in the then block, that is a registered callback, will be fired.

Here I test clicking the new-folder button, appearance of the dialog and its title, typing the name of new folder and clicking the create-folder-btn button. The start call executes QUnit's verifier which evaluates the equal statements.

At the same time the query that was fired by the create-folder-btn button is in progress. To verify its result, I start next the asyncTest call. Using the same when-then mechanism I wait while the frame is reloaded with the result of the operation and check it. So, here you are.
My point here is that more complex behavior and scenarios will be clear and readable as well. And I will show it in my upcoming blogs.

2 comments:

  1. Quite an interesting approach.
    I did a few experiments following your example..

    When you do
    _$('#create-folder-btn').click();
    I assume you expect the whole iFrame to reload... and this is why you have your next test
    'check new folder' which will wake up $.when( frame.load() )

    Indeed, you have a handler in this case..
    but what if clicking was triggering an ajax action.. can you check have another part of the test executed after the underlying ajax call and the callbacks it probably comes with ??

    (I'm quite new to those "Deferred Objects")

    Regards.

    ReplyDelete
  2. In such cases I am using events. I mean that the ajax callbacks can trigger an event that you can control in your test frame.bind(event_name, function() { ... }) Such binging you can enclose with appropriate deferred object.

    ReplyDelete