HTMLUnit is great to test web applications but sadly it doesn’t support recent js libraries (AngularJs/JQuery to not say their names).
PhantomJs is a great alternative but sometimes setup can be boring or too complicated for what you need.
To avoid to spend time on setups I added to “Rule Them All!” project a simple PhantomJs rule. Idea is really to just manage the lifecycle of PhantomJs and to provide a getDriver() method:
public class JQueryClientGeneratorTest { @ClassRule public static final PhantomJsRule PHANTOM_JS = new PhantomJsRule(); @Test public void doTest() { assertTrue(PHANTOM_JS.get("http://....").contains("OK")); } @Test public void angular() { final PhantomJSDriver driver = PHANTOM_JS.getDriver(); driver.get(home); driver.executeAsyncScript( // wait for angular to have done its work "var callback = arguments[arguments.length - 1];" + "var e1 = document.querySelector('body');" + "if (window.angular) {" + "angular.element(e1).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);" + "} else {callback()}" ); final String pageSource = driver.getPageSource(); assertTrue(pageSource, pageSource.contains("success")); } }
So basically the setup is pretty simple and then usage is PhantomJSDriver one (can be used as any selenium WebDriver but few specific methods like executeAsyncScript are quite interesting ;)).
Why did I need it?
Writing a javascript client generator for JAX-RS endpoint (https://github.com/rmannibucau/jaxrs-js-client) I needed to test it.
For JAX-RS it was quite trivial: just use OpenEJB ApplicationComposer:
@SimpleLog @EnableServices("jaxrs") public class JQueryClientGeneratorTest { @Rule public final ApplicationComposerRule rule = new ApplicationComposerRule(this); @RandomPort("httpejbd") private URL httpEjbdPort; @Module @Classes(innerClassesAsBean = true) public WebApp war() { return new WebApp(); } @Test public void js() { // TODO } @ApplicationPath("api") public static class App extends Application {} @Path("simple-resource") public static class SimpleResource { @GET public String aGet() { return "get"; } } }
So what do I have with it:
- A JAX-RS application deployed
- on a random port
- (bonus) nice logs (one line)
Now how do I test my JQuery client?
First point was to write a html page…yes but I don’t have a real war…but I have a light servlet support with OpenEJB embedded.
So finally I wrote a servlet serving my html page (I could have copy the content from a file but for the test and the need I made it simple and wrote the html directly in the servlet):
@WebServlet(urlPatterns = "/home") public class Home extends HttpServlet { @Override protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write( "<html>" + "<head>" + " <script src=\"jquery.js\"></script>" + " <script src=\"api?jqueryclient=myClient\"></script>" + "</head>" + "<body>" + " <div id=\"content\"></div>" + " <script>" + " $(function () {" + " myClient.SimpleResource.aGet()" + " .done(function (d) {" + " $('#content').html(d);" + " });" " });" + " </script>" + "</body>" + "</html>" ); } }
This html page needs 2 scripts:
- api?jqueryclient=myClient: this one is easy: generated by the lib so nothing to do
- jquery.js: here we’ll use the same hack as for the html page but reading content from a file
@WebServlet(urlPatterns = "/jquery.js") public class JQuery extends HttpServlet { @Override protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/javascript"); final ClassLoader loader = Thread.currentThread().getContextClassLoader(); final String jquery = IO.slurp(loader.getResourceAsStream("jquery-2.1.1.js")); resp.getWriter().write(jquery); } }
Now all is setup and running the program as a main works…but how to test it?
I started to test using HtmlUnit…but it doesn’t parse jquery correctly. So finally I fallback on PhantomJs and wrote the rule from Rule Them All!
Finally the test looks like:
@SimpleLog @EnableServices("jaxrs") public class JQueryClientGeneratorTest { @ClassRule public static final PhantomJsRule PHANTOM_JS = new PhantomJsRule(); @Rule public final ApplicationComposerRule rule = new ApplicationComposerRule(this); @RandomPort("httpejbd") private URL httpEjbdPort; @Module @Classes(innerClassesAsBean = true) public WebApp war() { return new WebApp(); } @Test public void generation() throws Exception { assertTrue(PHANTOM_JS.get(httpEjbdPort.toExternalForm() + "openejb/home").contains("get")); } @ApplicationPath("api") public static class App extends Application {} @Path("simple-resource") public static class SimpleResource { @GET public String aGet() { return "get"; } } @WebServlet(urlPatterns = "/home") public static class Home extends HttpServlet { @Override protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write( "<html>" + "<head>" + "<script src=\"jquery.js\"></script>" + "<script src=\"api?jqueryclient=myClient\"></script>" + "</head>" + "<body>" + "<div id=\"content\"></div>" + "<script>" + "$(function () {" + "myClient.SimpleResource.aGet()" + ".done(function (d) {" + "$('#content').html(d);" + "});});" + "</script>" + "</body>" + "</html>" ); } } @WebServlet(urlPatterns = "/jquery.js") public static class JQuery extends HttpServlet { @Override protected void service(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("application/javascript"); final String jquery = IO.slurp(Thread.currentThread().getContextClassLoader().getResourceAsStream("jquery-2.1.1.js")); resp.getWriter().write(jquery); } } }
Nothing very complicated, it not as slow as starting a complete server (in particular when multiplying tests) and it is easily debuggable :).