Tuesday, October 30, 2007

Introducing JSFUnit

A few months ago, Julien Viet, creator of JBoss Portal, sends me an email. He says, "Hey JSF guy, why are there no good testing tools for JSF? I need a tool for this. Maybe you can help?"

I took a look and he was (mostly) right. There are a couple of mock object frameworks for JSF as part of Shale and Seam but they are there primarily for testing artifacts you might create for those frameworks. They are really not general testing tools for JSF applications. And they don't help with integration testing at all.

So I started thinking up what a general JSF testing tool might look like. Here is what I came up with:
  • No mock objects!!! I really hate mocks. I hate writing mocks. I hate maintaining mocks. And I hate knowing that my mocks don't simulate the environment well enough. But most of all, I hate it when I have to add stupid init() methods to my application code - just for the mocks. And sooner or later I know my tests will fail because of the mocks! BTW, here is a saner discussion of Mock vs. In-Container testing.
  • Leverage the JSF API JSF provides tons of information about a running application. If I want to assert that my JSF application state is correct, I should be able to just ask JSF.
  • Use plain old JUnit If I want anybody else to use the tool, they shouldn't need to learn how to write tests all over again. JUnit has stood the test of time. It works well and everybody knows how to use it.
  • Allow navigation testing A big part of validating JSF is validating that a given user input will navigate to the proper page view.
  • Test a JSF App as it is really deployed and run I suspect that this is why nobody has built a full JSF testing framework yet. A real JSF request might start with an AJAX component that fires an HTTP request, then pass through servlet filters, hit the FacesServlet, the JSF lifecycle, a myriad of PhaseListeners, do magic Seam stuff at every turn, and then all get rendered with Facelets. How can you possibly test all that stuff together with a mock framework?
So it has to be an in-container thing like Apache Cactus. But Cactus would have me instantiate a FacesServlet and call it directly. That won't quite cut it. What I really need is something that runs in the browser to do real HTTP requests and some kind of JUnit test on the server that lets me do asserts when the request is done. Why not a JUnit test that does both at the same time in the same JVM? Then I can assert that the server side state is correct and the client state is correct - all in the same test.

JSFUnit is done
I just combine Cactus and HttpUnit and I'm done, right? It took six classes to tie it all together. Cactus lets me run JUnit tests in-container. I can make real HTTP calls with HttpUnit, then test the HTML that comes back. And with a few FacesContext tricks, I have access to all the real server-side JSF objects for navigation, managed beans, EL, the component tree, etc. So I can do real JSF requests, test the real HTML that comes back, and test the real state of the real server-side objects.

For real. All in one JUnit test.

Only six classes. All that and only six little classes! Julien will really like it.

Julien hates it
"Stan, this sucks." OK, he didn't exactly say that. At JBoss, they don't just come out and tell you your code sucks. They use a lot more cuss words and throw in an SMD or two.

He told me it needed a simpler API and he was right. I wrote some Facade classes and got a Hello World test down to about five or six lines of code:

JSFClientSession client = new JSFClientSession("/hello.jsf");
JSFServerSession server = new JSFServerSession(client);
client.setParameter("name", "Stan");
assertEquals("Stan", server.getManagedBeanValue("#{myBean.name}"));
assertTrue(client.getWebResponse().getText().contains("Hello, Stan"));

JSFUnit is just getting started
So now I'm done? Actually, that was just the beginning. It turns out that AJAX components need special handling so I've added support for most RichFaces components. Matt Wringe of the JBoss Portal team added Ant integration. Then Dennis Byrne from ThoughtWorks and MyFaces came on board to work on JSF static analysis tools. This is getting big and broad real fast.

There is still a lot to be done. We need to finish the support for the last few RichFaces components and then move on to other popular component libraries. We need better Maven integration so that it is on par with the Ant tool (JSFUnit tests itself with JSFUnit and Maven. So you can run your tests from Maven, but it needs to be easier). Dennis has lots of good ideas for improving the JSF static analysis. Who knows, at some point maybe we'll even add mocks (maybe not).

JSFUnit Beta 1
No matter what, we are doing our first Beta release on November 30. Right now you have to download JSFUnit source and build it yourself with Maven. Even so, we've got a lot of folks out there who have done that and are already finding JSFUnit to be a useful tool. So to me, as long as it passes the usefulness test, it's time for Beta. Stay tuned for details.

In the mean time, check out http://www.jsfunit.org.

So long, and thanks for all the fish,

Stan Silvert