Writing JavaScript Tests with Jasmine for RAP

rapjasmine trnsp Writing JavaScript Tests with Jasmine for RAP

If you are writing unit tests for your Java code, then you should also write them for any other code you wrote for the same project. Otherwise these tests only give you a false sense of security. RAP 2.0 provides a new JavaScript API to extend the framework with custom components, as you can read here. Using third party JavaScript libaries (jQuery, D3, etc) or widgets/applications (like GMaps, CKEditor… ) makes the task manageable even if you are not an HTML/CSS/JavaScript expert. But what about tests?

As Java/RCP/RAP developer you may be used to JUnit, and perhaps Mockito (if not: read this). Now, Java/JavaScript differences aside, I like Jasmine even better. I used it to test my JavaScript code when porting the CKEditor add-on for RAP (demo, download) to RAP 2.0. In this article I will use this project to demonstrate one way to use Jasmine with RAP.
You can look at the editors Java implementation here, and at the JavaScript code we want to test here.

Remote API Basics

To fully understand the examples i’ll show here, you should be familiar with the RAP Remote API, but you’ll get the gist of it either way. If you want, you can jump straight to the next chapter.

A brief introduction to Remote API is also given here. Basically, each side (client and server) have one “half” of the custom widget, and a representation of the other side’s “half” as an instance of RemoteObject:

half objects Writing JavaScript Tests with Jasmine for RAP

Therefore RAP includes a RemoteObject implementation for Java and one for JavaScript. The main difference is that the server can create new remote objects…

Connection connection = RWT.getUISession().getConnection();
RemoteObject remoteObject 
  = connection.createRemoteObject( "MyWidget" );

… while the client can only get existing ones:

  var remoteObject = rap.getRemoteObject( widgetInstance );

In addition, the Server can also receive remote operations via the remote object, while the client object has to implement methods (especially setter) that can be directly called by the framework.

Typically, a simple custom widgets client-side life cycle looks like this:

  • The client component has to register a TypeHandler (once). This is basically the glue between your custom widget and the framework. The type handler consist of a factory for the client object, and a list of properties, methods and events it supports.
  • The server side creates an RemoteObject instance. With this, a “create” operation is send to the client and invokes the type handler’s factory.
  • Using the id of a Composite (which the server typically provides in a “set” operation), the custom widget can obtain the client side instance and attach itself to it: rap.getObject( parentId ).append( htmlElement );
  • All invocations of “set” or “call” on the server side RemoteObject will cause the matching methods of the client widget to be called. To send messags back, the client widget can itself obtain a RemoteObject to send operations to the server.
  • When the server remote object is destroyed, so is the client object.

Jasmine at a glance

The Jasmine project site already gives a good tutorial, so I’ll try to keep this short. Jasmine allows for Behavior-Driven Development (BDD), which is similar to Test-Driven Development (TDD). It basically means that your tests will read like a “story” written in plain English. (Or any other language, if you’re crazy.)

For example, a traditional test in JUnit…

public class Math_Test extends TestCase
  public void test_RoundsUp2Point5() {
    assertEquals( 3, Math.round( 2.5 ) );
  }
};

… may look like this in Jasmine:

describe("Math", function() {
  it("should round 2.5 to 3", function() {
    expect( Math.round( 2.5 ).toBe( 3 );
  });
});

As you can see it’s not that different at first, except it’s easier to describe what exactly you are testing. The “describe” function seems similar to a test class (it is called a “Suite”), “it” to a test method (actually a “Spec”), and “expect” looks a bit like Mockitos “verify”. The “setUp” and “tearDown” methods in JUnit would be “beforeEach” and “afterEach” functions in Jasmine.

However, the advantage is that unlike JUnit tests, “describe” functions can be nested. And what I personally love about that, is that each can have it’s very own beforeEach/afterEach functions, and they are executed for all the “describe” functions the “it” is in. That made my tests a lot more readable. In JUnit I would often have a lot of similar setup code at the beginning for each test, and multiple asserts the end. Sure, there ways to reduce it, but I think it’s a pain. My Jasmine Specs on the other hand are usually around three lines only.

jasminegraph Writing JavaScript Tests with Jasmine for RAP

What to test?

There may be several parts to your JavaScript component. The RAP type handler has little to no logic of it’s own, and would require a “real” RAP client to be tested. Therefore we omit it, and only test the widget “class” itself.

If your custom widget is basically a wrapper for an existing JavaScript app (like CKEditor), then you might want to test only the wrapper. If so, you can also replace the app with your own mock, though you have to know the app’s API really well for that to work. In my case, I first did a untested prototype to figure out how to get the CKEditor to cooperate with RAP. Then I started again from scratch, writing tests first using a CKEditor mock I created myself.

If you are writing the entire widget yourself, you should of course test all of it, not just the part that connects to RAP.

Setting Up Jasmine with a RAP enviroment

Download the Jasmine Standalone Release and unzip it to a folder in your test project. You don’t need the included examples, so delete the src and specs folder. Open the SpecRunner.html in an editor. We are interested in the <script> tags only. Remove those that point to the deleted files. Then get the “rap-mock.js from our GitHub repository and include it with a new script tag. This is key, as the file contains a mock implementation of the RAP JavaScript API, so that your custom component can be tested outside an actual RAP application.

Below the rap-mock.js, add the JavaScript file(s) from your own RAP JavaScript component. If they are in another bundle, it may have to look like this:

<script 
  type="text/javascript" 
  src="../../com.eclipsesource.widgets.ckeditor/src/resources/handler.js">
</script>


Finally, create a new JavaScript file like “CKEditorSpec.js” and also include it in the HTML file. This is where we will put the tests.

The rap mock is initialized automatically by including the file itself, but to be sure that everything is reset with each test, your outmost “beforeEach” should look like this:

 beforeEach( function() {
  rap = new RapMock();
} );

In addition to the official API of the “rap” object, this one has two fields “fakeRemoteObject” and “fakeComposite”. Like “rap” itself, they are only empty implementations of the RemoteObject and Composite types, which are returned if rap.getRemoteObject() or rap.getObject() (respectively) are called. That makes it a lot easier to test interactions with these objects later on. (Note that it always returns the same instances, but since they dont have any state it shouldn’t matter.)

My First Spec

Since variables have function scope in JavaScript, and all Suites and Specs are functions, you can freely define variables within any suite and share them with the contained Specs. I would suggest to do so for your outmost Suite for the instance of your custom widget. For me, it looks like this:

describe( "eclipsesource.CKEditor", function() {
 
  var editor;
 
  var createEditor = function() {
    editor = new eclipsesource.CKEditor( { "parent" : "w2" } );
  };
 
  beforeEach( function() {
    rap = new RapMock();
    editor = null;
  } );
 
  afterEach( function() {
    editor = null;
  } );
 
  ...
 
} );

I’m not creating an instance in the outer Suite because we will sometimes need to do some setup before calling the constructor. This is where spies come into play. Remember, our rap mock (and in this case CKEditor mock) does absolutely nothing itself (except preventing crashes). To write meaningful tests, we need to spy on it.

Spies replace methods with stubs, which can later be checked for invocations Mockito-style. They can also return any value, capture arguments, or execute arbitrary code. Read more about them here. Spies are automatically removed after each it.

Now we dive right into it, and test that the widget adds itself (specifically it’s element) to the parent:

  describe( "The Constructor", function() {
 
    it( "should get a Composite with the parent id", function() {
      spyOn( rap, "getObject" ).andCallThrough();
      createEditor();
      expect( rap.getObject ).toHaveBeenCalledWith( "w2" );
    } );
 
    it( "should add an element to the composite", function() {
      spyOn( rap.fakeComposite, "append" );
      createEditor();
      expect( rap.fakeComposite.append )
       .toHaveBeenCalledWith( editor.element );
    } );
 
  } );

It is a common pattern to prefix “private” fields of JavaScript objects with a “_”. For the CKEditor wrapper I completely abstained from creating private fields and made everything “public”, including the HTML element. If I hadn’t, I would either have to access a private field, or examine the contents of the element that was given to the fake composite. Since i’m not interested in testing the CKEditor itself, I chose this simpler solution.

Most other aspects of the constructor and the editor/wrapper are tested in similar ways, as you can read in the full Spec file. However, there are a few more special cases I like to discuss in detail.

Testing events

The CKEditor wrapper registers events listener on the CKEditor itself and on the “rap” object, e.g. to check for changes before a request is send to the server. There are various ways to test them. The “cleanest” one would likely be to obtain the listener with a spy and execute it:

describe( "The send listener", function() {
 
  var onSendListener;
 
  beforeEach( function() {
    spyOn( rap, "on" ).andCallFake( function( event, listener ) {
      if( event === "send" ) {
        onSendListener = listener;
      }
    } );
    createEditor();
  } );
 
  describe( "of an editor that has not changed", function() {
 
    it( "sends nothing", function() {
      spyOn( rap.fakeRemoteObject, "set" );
      onSendListener();
      expect( rap.fakeRemoteObject.set ).not.toHaveBeenCalled();
    } );
 
  } );

With the “beforeEach” function this does not bloat your code all that much, but it’s still a couple of lines. So instead I simply made the listener “public” (like with the HTML element) and called it after checking it was registered. This is part of the “contructor” Suite show above:

it( "should add a send listener", function() {
  spyOn( rap, "on" );
  createEditor();
  expect( rap.on ).toHaveBeenCalledWith( "send", editor.onSend );
} );

Now I can simply invoke editor.onSend whenever I want, since I now it has been registered with the RAP client, which I trust to work correctly. But, there is one important bit: You should not simply write editor.onSend(), because this would mean that editor will be the context (this) of the invokation. This is not automatically given when working with JavaScript callbacks, and I know it is not the case with rap.on. (To see how I solved that issue for the CKEditor, look at the bindAll function in the source code.) Instead, you can write editor.onSend.call();, which executes it without context.

The Clock makes “tick”

A common issue with JavaScript testing are asynchronously executed function. This custom widget has one of those for setting the editors font, which happens with a small delay using the JavaScript setTimeout function. This means I can not simply call editor.setFont and check the results right away. Jasmine includes two different solutions for this issue. If you want or need your test to emulate the actual runtime scenario, you need to wrap the code of your Spec in runs and waitsFor functions.

Since I use only a mock CKEditor I had no such requirement, and used the alternative: Simply mocking the JavaScript timer.

describe( "The setFont function", function() {
 
   var body;
 
   beforeEach( function() {
    jasmine.Clock.useMock();
    createEditor();
    body = editor.editor.document.getBody();
    spyOn( body, "setStyle" );
    editor.setFont( "13px Arial" );
  } );
 
  it( "calls body.setStyle on a ready event", function() {
    editor.onReady.call();
    jasmine.Clock.tick( 0 );
    expect( body.setStyle ).toHaveBeenCalledWith( "font", "13px Arial" );
  } );
 
} );

Feedback…

…would be welcome, especially if you have trouble writing tests for RAP custom widgets. I did not touch on some topics like sending events to the server (which the CKEditor doesn’t do), or how to test DOM changes, but that would have completely burst the scope of this article. (I sometimes have issus keeping things short…) Also, on testing the server side: RAP includes a Fixture (not official API) which allows testing the Remote API, but we might change it soon, so I didn’t want to write about it now. If you are curious, look at CKEditor_Test.java.

Final Words

Jasmine is awesome. Go use it for something.

You may also like...

Share this Post

Twitter13
Google+0
LinkedIn
Facebook

Tags

Comments are closed.

No responses yet

Written by . Published in Categories: EclipseSource News, Planet Eclipse