Screen Grab-Brown bear-Ryan McVay-Digital Vision-AA045492

JavaScript to test? Jasmine helps. Just ask our Dev Team

February 8, 2012 | By Jason Racey | By Devs, For Devs, Technology

Recently my team was given the opportunity to change the user interface layer of www.GettyImages.com (GI) to help boost our organic search engine traffic. In this post, we’ll walk through some of the changes we made to re-factor our existing code and, by doing so, making our JavaScript testable in the process.

Brown-bear-Ryan-McVay-Digital-Vision-AA045492

(Photo by Ryan McVay/Digital Vision/Getty Images AA045492

What’s an ADP?

Every one of our image, footage and audio files on gettyimages.com is referred to as an asset, and all of the details associated with an asset are viewable on its Asset Detail Page (ADP).  Here’s the ADP for asset AA045492:

 

To navigate between ADPs, customers click the left/right arrow buttons in the upper right-hand corner, which originally called the JavaScript you see below:

 

Notice that in 25 lines of code it introduces 5 distinct external dependencies: ADCookie, AssetSlotTrackingCookie, SetADPCookie, TrackingManager and Redirector.  Also notice that it’s not particularly cohesive.  It’s responsible for setting values on cookies, doing some sort of tracking, building a URL and finally, redirecting to that URL.  Our task was to replace the original version of the URL (http://www.gettyimages.com/detail/AA045492) at the end of the function with the SEO-friendly version requested by the business (http://www.gettyimages.com/detail/photo/brown-bear-roaring-side-view-royalty-free-image/AA045492).

Making our Code Testable

Essentially we wanted our SEO-friendly URL tests to fail when we failed to provide the SEO-friendly URL and not for any other reason. To accomplish this we at least had to deal with any of the external dependencies above which would interfere with testing. We also decided to move each of the responsibilities above into their own object. This would make the existing code more cohesive and enable us to isolate the SEO-friendly URL code into its own object.

Starting from the top, we needed to wrap the ADCookie, AssetSlotTrackingCookie and SetADPCookie dependencies so that these could be injected as mocks from test code. We considered all of these to be a reasonable single responsibility under the heading of “cookie stuff.”

We used closures to do the wrapping, which are an extremely awesome thing.  For one thing they make it easy to write object-oriented JavaScript, which at least for us was important in making it testable. Notice the adpCookieGetter and adpAssetSlotTrackingCookieGetter function arguments. This is dependency injection. Unit tests can inject mocks while product code will not specify any function arguments, resulting in use of the real objects in the variable assignment code that follows.

The next unmocked dependency was TrackingManager. It turns out that whatever this object does, it did not need to be mocked. Our first unit test was getting past this line in the original implementation without any problem. So, on we went to the next dependency, the Redirector. Again we used a closure to make a mockable wrapper.

Now, all that was left to do was to wire all of these wrappers back together in a new version of the original function “ADPGoTo”. Once again this is written as a closure, so it is, itself, mockable, and external dependencies are passed in as function arguments so they can be mocked in unit tests.

 

Testing our Code

Jasmine is a “behavior-driven development framework for testing your JavaScript code” according to a very brief description at its github repository. This was our first extensive use of Jasmine and coming from a lot of experience with Moq we found it relatively easy to understand.

Since Jasmine calls itself a BDD framework, and because we have become comfortable with Given-When-Then scenarios, we chose this as our style. What you see here is the basic structure of the AdpTrafficController test fixture written in JavaScript using the Jasmine functions “describe” and “it”. The “describe” function allows you to create scope for your test setup. The “it” functions are where you make your asserts. I added the arrange-act-assert comments to make this a little clearer. Keep in mind that other than having at least one “describe” and one “it” nested inside it, this is just our approach to using Jasmine and it could have been done another way.

Now I’ll add in the arrange steps. The first object declared is an instance of the object under test, the AdpTrafficController. Following this is a set of “any” objects to hold some fake test data. Following that is a set of “mock” objects that will be the targets of Jasmine spies.  Notice that the declarations are scoped separately from the assignments. The variable declarations here are in a scope that is global to both the “beforeEach” and “it” functions so they can be accessed by both.  However, the assignments must be in the “beforeEach” to retain their value at runtime.

I mentioned Jasmine spies. In the code below you see calls to “spyOn”. These are setting up mock objects that wrap the object they are spying on. In our case we wanted to mock calls out to our external dependencies: the cookie setter, the URL provider and the redirector. This is the simplest possible way to set up mocks in Jasmine. What you see are a set of stubs replacing void methods. Jasmine spies can do a lot more, much like mocks in Moq. They can even call through to the real object they are wrapping if you just want to monitor interactions (more information on spies here). The last line initializes the object under test with our mock objects.

Here, the one and only act step has been added. Our decision to nest “describe” and “beforeEach” calls to represent the “When” and “act” is simply a matter of style. We could have placed the call to adpGoTo in the parent “beforeEach” but we like the approach below better. There’s a clear Given-When description and it maps directly to the arrange-act steps in code. Again, the call to adpGoTo must be inside the scope of a “beforeEach” for any of the objects to retain their value at runtime.

 

There you have it! While there are other scenarios that we defined and implemented for this work, they generally follow the same structure as outlined here.  Below is the generated output from running the spec runner file in a browser.

More posts by this author

  • http://www.thebeatles.com/ George Harrison

    Since Jasmine is a “behavior-driven development framework for testing your JavaScript code” I am curious as to why you used it as a unit test framework in this case. Given(“that I am using the object that I am testing”) and another Given(“I am calling thing thing I want to test”) and then some It(“Should do stuff that the method should do”)

    It seems as if you missed an opportunity to write the tests in a specification-esque manner. Can you provide some insight into the ideas of Behavior/Acceptance Driven Testing and Javascript and why you took this route?

  • Tom Kleinecke

    Having tests written in a Given/When/Then format (Behavior/Acceptance) is no different then writing a test in a Setup/Execution/Validation format. The function names above are longer (overly so in my opinion) but I know I have tested what the product owner signed off on.
    At least the long names are better than some of the really short ones (have you seen any with names like ‘item’)

    What I like about Jasmine is it is designed to have multiple asserts (its) in a single test. If any of asserts fail the remaining ones are still executed. There is nothing worse than thinking I have one last test to fix only to find that it was testing half a dozen things. Actually I can think of one thing worse. Not having unit tests at all.

  • Jason

    George, thanks for the question. There’s a lot of support for BDD at Getty Images. In fact an upcoming post on this blog will be on that very subject. In this particular case our goal was not to use Jasmine to write a specification but to take some legacy JavaScript, add some pinning tests, and then refactor it while adding some new functionality. What isn’t mentioned in the blog post is that we also have an executable specification written using SpecFlow that exercises this JavaScript, but is bound to the code stack at a higher level. We’re pretty impressed by how easy it is to use Jasmine, even when working with some test-unfriendly legacy code. Hopefully you were able to get a feel for that from the example.