Sunday, December 9, 2012

Unit tests with Jamine

Attention: This is old stuff. New build instructions here

Today we've got the unit tests done for all our JS files. I know, I should have written my tests before the actual implementation. Bad bad programmer! I never write tests first but afterwards. Does it count?

These tests aren't integration tests. They are interested on a small peace of code (unit) at a time with no external context dependency. We don't need to test if the browser triggered a 'click' event or if a given framework drew the expected tag in the browser. We assume they did. Hopefully the team who created the browser and the framework have already tested it for us. Also, we don't need to have a real service running at the back-end in order to test our UI. Ideally, a unit test wouldn't take more than one second, and a server running behind the scenes would blow up this ideal.

0.03 seconds to complete 41 tests

The trick is to create mock objects whenever you need them. Our 'ApplicationChannel.js' message bus (Read more about client-side messaging in this great blog entry) and the javascript nature itself give so much flexibility that you don't feel the pain on mocking data. Almost every thing in JS is a map. So, if you need to mock something, just create a map and you are fine. The next feel lines show you some JS tests tricks.

beforeEach and afterEach functions

When you are creating tests, you are the boss. You are free to play with your code in between the the lines of your unit test file. You can mock whatever you want. You can create temporary objects. You can even change the implementation of any object, including those defined by 'window'. You just need to remember to clean-up you mess before leaving. Jasmine provides two handy callbacks for that: beforeEach and afterEach.

Imagine that the lines you are testing use the 'window.setTimeout' function, making it an asynchronous peace of code. How would you test it? You could mock the setTimeout function.

Note that it is very important to set the original 'setTimeout' method back otherwise subsequent tests would give bizarre results.  

Use the ApplicationChannel.js

Imagine we want to test the line 28 of our ApplicationController.js module.

The application listens for 'new-local-file' events in order to trigger an upload request. You don't need to know who sends this event. For now, your only concern is to check if the application sends a upload request as result of 'new-local-file' events. You want to check if the upload request has the expected parameters.

Note that we also mock the 'sendMessage' function of ApplicationModel.js. We will test this function in another test file.

Create a temporary HtmlElement container

Sometimes there is no way to test a code without testing its html output. All the tests share the same browser window. Therefore, they share the same '<body></body>'. To avoid leaving behind extra html tags, create a temporary container for your tests.

Mock third-party objects

See this extract of the 'view/FileView' module.

We are already able to test if the 'container-rendered' event triggers the creation of a new 'svg' tag, but how to test the internal 'drop' event implemented by d3? How to trigger this event? It is time to hack the d3 code a little bit.

Download the development version of the d3 library and save this file under the 'project/photodb/photodb-gui/src/main/webapp/app/lib/d3' directory. Now change the 'project/photodb/photodb-gui/src/main/webapp/app/config.js' by pointing the 'lib/d3' module to this file.

You are now using the new d3 file.

When is the 'on' method called? Let's go step-by-step. Open the dev tools and put a break point in the line where the application listen for the event 'container-rendered'.

Now execute the tests again. Go step-by-step until you hit the 'on' method.

Ta-da! 'd3_selectionPrototype' is the object to blame! But do we have access to it? It seems to be a private object. Fortunately d3 gives access to it via the global 'd3' object.

Now you know you need to mock the 'd3.selection.prototype.on' function. Wrap this function call. You just want to grab the callback function of the 'on' event.

Once you have the dropCallback function, you can simply call it to test whatever you want.

Check out the latest photodb code

Our application is evolving. It is getting fancy. Go to github to checkout the latest version of it. Remember, you need to execute 'make run-jasmine' to start the jasmine tests.

tveronezi@botobox:~/dev/ws/project/photodb$ make run-jasmine

Check more on how to run our custom TomEE server here.

No comments:

Post a Comment