Saturday, 9 August 2008

jqMock - JavaScript Mock Framework for jQuery / jqUnit

It's been well over a week since I release version 1 of the jqMock library, and I've finally finished the documentation including the jqMock user guide, which is pretty comprehensive.

It has all the trimmings of your standard mock library, although I'm quick eager to wait for comments on what people think of the different approach to mocking, and whether this library is useful for everyday unit testing usage.

Please have a look at the code, do some code review, and leave some comments!

Links

Wednesday, 23 July 2008

Global Eval in Rhino

I've been working to complete John Resig's env.js simulated browser environment in Rhino so that it will run unit tests from the command line in exactly the same way as the browser. The end goal was to get behaviour in env.js to a point where there are practically no differences with a real browser, so that enterprises can incorporate running javascript unit tests into their CI environment.

Using the jquery QUnit test suite as my benchmark, I've been trying make the tests pass. The going has been tough, with countless nuances to fix up, but I managed to pass the 1.1.4 core tests, and the 1.2.6 ajax and event tests. However, now stumped on the 1.2.6 core tests and realising that I won't ever have enough spare time to implement full CSS support, I'm going to blog about other areas which I've been successful with.

One change that made a big difference was the additional of a global eval. This means adding script elements will evaluate them in the global scope, and ajax loading of dynamic scripts with ajax now works as expected.

The trick is to use rhino's load() function, which loads javascript from a file system or URL location and evaluates it in the global scope. To load arbitrary snippets of javascript, the text is written to a temporary file on the filesystem, load() is called, then the file is deleted. Luckily, env.js has already implemented writing and deleting files!



.....

  // run node through execScripts whenever added to the dom
  appendChild: function(node){
    this._dom.appendChild( node._dom );
    execScripts(node);
  },
  insertBefore: function(node,before){
    this._dom.insertBefore( node._dom, before ? before._dom : before );
    execScripts(node);
  },

.....  

  function execScripts(node) {
    if ( node.nodeName == "SCRIPT" ) {
      if ( !node.getAttribute("src") ) {
        globalEval ( node.textContent );     
      } else {
        var src = node.getAttribute("src");
        load(src); // you'll actually have to resolve relative URLs here   
      }
      if (node.onload && typeof node.onload == "function") {
        node.onload();
      }     
    } else if (node.nodeType==1) {  
      var scripts = node.getElementsByTagName("script");
      for ( var i = 0; i < scripts.length; i++ ) {
        execScripts( scripts[i] );
      }
    }
  }
  var globalEvalCounter = (new Date()).getTime();  // temp file name
  function globalEval(data) {
    try {                        // write to java temp directory
      var javatmpdir = java.lang.System.getProperty("java.io.tmpdir")+"";
      var folder = "file:///" + javatmpdir.replace(/\\/g, "/");
      var tempfile = folder + (globalEvalCounter++);
      var xhrPut = new XMLHttpRequest();
      xhrPut.open("PUT", tempfile, false);
      xhrPut.send(data);
      load(tempfile);
      xhrPut = null;
      var xhrDel = new XMLHttpRequest();
      xhrDel.open("DELETE", tempfile, false);
      xhrDel.send();
      xhrDel = null;
    } catch(ex) {
      throw new Error("Error occurred");
    }
  }
 

This mechanism allows you to mimic browser behaviour much more closely, and load the scripts defined in the HTML file (like a real browser) rather than a separate js file to load the unit tests. Doing that also allows you to fire the document ready event at the right time. More on this in my next post.

Monday, 24 March 2008

PortalDelegateServlet – Servlet Session Sharing in Liferay

After finishing my blog entry Liferay Session Sharing Demystified, i found a forum post which pointed to the liferay Jira of LEP-2297. Turns out Liferay 4.3.x has built in support for running servlets from within the Portal context! Yes... yet another wonderful but undocumented feature in Liferay.

In a nutshell, you configure your servlet to register itself with the built in PortalDelegatorServlet. This servlet (in the portal’s context) makes a cross-context call to your servlet, and provides your servlet with the portal session.

From a session sharing point of view, this behaves exactly the same as portlets using the portal session (i.e. portlets set to non-private session)

  • full access to portal attributes
  • full access to non-private portlet attributes, regardless of WAR file
  • Shared attributes set by the servlet will be made visible to the private portlets
  • no access to the WAR session attributes (servlets and private portlets in the same war)

Configuration Example

The example configuration in the Jira is based on the code that was submitted, so here is an example based on the Liferay’s distribution. In your servlet’s web.xml file, configure the following servlet.

<servlet>
  <servlet-name>springdemo</servlet-name>
  <servlet-class>
    com.liferay.portal.kernel.servlet.PortalDelegateServlet
  </servlet-class>
  <init-param>
    <param-name>servlet-class</param-name>
    <param-value>
      org.springframework.web.servlet.DispatcherServlet
    </param-value>
  </init-param>
  <init-param>
    <param-name>sub-context</param-name>
    <param-value>downloads</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

This example registers your servlet example.FooServlet under the key foo.do. (If a sub-context is not provided, the servlet-name will be used as the key instead.) When a request is made to /delegate/foo.do , the PortalDelegateServlet will process the URL, lookup the key foo.do, find your servlet, and make a cross context call to your servlet to run the service method. If you don’t like the /delegate servlet-mapping, you can change this in the portal’s ROOT.war web.xml.

Limitation

One confusing thing is that the sub-context is used as the key to store the servlet, while only the first token of the URI is used as the key to lookup the servlet. Which means if you try to set the sub-context to myapp/foo.do then navigate to the URI of /delegate/myapp/foo.do , it won’t work, because the PortalDelegateServlet will attempt to lookup the map using the value myapp, and find nothing. Bottom line: the sub-context must be only single level deep.

Using Spring DispatcherServlet

If you use Spring as your front-controller, you can configure more sophisticated URLs. The DispatcherServlet can be configured for use with the PortalDelegateServlet as follows:

<servlet>
  <servlet-name>springdemo</servlet-name>
  <servlet-class>
    com.liferay.portal.kernel.servlet.PortalDelegateServlet
  </servlet-class>
  <init-param>
    <param-name>servlet-class</param-name>
    <param-value>
   org.springframework.web.servlet.DispatcherServlet
 </param-value>
  </init-param>
  <init-param>
    <param-name>sub-context</param-name>
    <param-value>downloads</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>

Then in the springdemo-servlet.xml, you will have something like this, to configure a mapping to the URL of /delegate/downloads/getdocument.do

<bean id="myDownloadController" class="example.SpringServletController" />
<bean id="urlMapping" 
    class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  <property name="mappings">
    <props>
      <prop key="/downloads/getdocument.do">myDownloadController</prop>  
    </props>
  </property>
</bean>

Caveats

You will need to be careful if you are using third party libraries like Spring. These libraries don’t expect you to be invoking a cross context call, so anything can happen!

For example, when wiring up the Spring DispatcherServlet, writing directly to the response output was fine, but returning a ModelAndView didn’t work. Further investigation revealed that InternalResourceViewResolver was looking in ROOT.war to resolve the location of the view. Which means if the prefix was /WEB-INF/jsp/ , you would have to put this inside /ROOT.war/WEB-INF/jsp , which is clearly not workable.

However, servlets are usually deployed in the portal is to enable some sort of file download, which requires you to write directly to the response output stream anyway, and for this use case the PortalDelegateServlet is the perfect tool.