Summary
This article briefly explains Cactus, its common uses and advantages and then provides a step-by-step tutorial on how to use the Cactus integration provided by WTP. The article assumes that you are familiar with JUnit and the basics of using WTP to build, deploy and run web projects.Daniel R Somerfield, BEA Systems
May 31, 2005
Cactus is actually very, very simple. It is often valuable to be able to test components (plain Java classes, Servlets, EJBs, etc.) within the container in which they will eventually be deployed. Cactus provides proxies for unit tests so that although you run the vanilla JUnit client (or any other JUnit client) locally, the tests are actually invoked on the target container. A servlet within the container fields requests from your local test case and delegates that actual running of the test to the test case running on the server and reports back the results. The test case can be deployed locally or remotely. Either way, Cactus works the same since it uses HTTP to do the invocation and reporting.
Really, the only trick to using Cactus is setup and deployment. Fortunately, we get help from the Web Tools Project on the deployment. At the present, setup is still a manual process (something that will hopefully change in the future).
We start off with an existing web project and a class we want to test on Tomcat or any other WTP-supported servlet container. Let's say we have a PermissionManager class we want to test to see if it is correctly managing the permissions in the HTTP session. The important part of the PermissionManager is this method:
public void validate(HttpServletRequest request) throws InsufficientPermissionsException { Permission permission = (Permission) request.getSession().getAttribute(TOKEN_KEY); if (!permission.implies(fPermissionRequired)) { throw new InsufficientPermissionsException("You do not have permission for that operation"); } }
In theory, it should check whether the user has permissions for the operation and if not, it should throw an InsufficientPermissionsException. To check that it is doing it's job, we will want to write a unit test.
But first, we need to get the cactus dependencies available on the class path. You will want to put them in the WebModule under WEB-INF/lib so that they are visible in the web module at runtime. The dependencies are all from the Cactus 1.7 distribution (available at http://jakarta.apache.org/site/downloads/downloads_cactus.cgi). As of the writing of this document, they are:
Once you have added the libraries to the WEB-INF/lib (you can simply drag and drop them there), you can either add the libraries to the the build path by editing the build path in the project properties, or by right-clicking them while in the Java Perspective and choosing "Build Path->Add to Build Path".
Now that we have the dependencies on the classpath, we can write our test. Rather than the usual JUnit TestCase, we need to extend ServletTestCase. As in JUnit, the testXXX() methods are discovered via introspection and run when the test executes. Unless you have any special needs via the beginXXX() and endXXX() client-side methods, the test will really look like any other unit test (see the cactus docs for more details on using beginXXX() and endXXX()). First let's write a test method to make sure the PermissionManager class is allowing requests it is supposed to. In the JavaSource folder, create the PermissionManagerTest class and add the following method:
public void testPermissionManagerAllow() throws Exception { //Make sure that the AllPermission gives us permission to our test permission session.setAttribute(PermissionManager.TOKEN_KEY, new AllPermission()); PermissionManager manager = new PermissionManager(new TestPermission("Test")); manager.validate(request); }The test is quite simple. It just calls the validate method. If the validation fails, an InsufficientPermissionException will be thrown and the test will fail. Before we can run the test, we need to set up Cactus. In the web module, under WebContent/WEB-INF there should be a web.xml. To this file, add the following mark-up before the final web-app tag:
<filter> <filter-name>FilterRedirector</filter-name> <filter-class>org.apache.cactus.server.FilterTestRedirector</filter-class> </filter> <filter-mapping> <filter-name>FilterRedirector</filter-name> <url-pattern>/FilterRedirector</url-pattern> </filter-mapping> <servlet> <servlet-name>ServletRedirector</servlet-name> <servlet-class>org.apache.cactus.server.ServletTestRedirector</servlet-class> </servlet> <servlet> <servlet-name>JspRedirector</servlet-name> <jsp-file>/jspRedirector.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>ServletRedirector</servlet-name> <url-pattern>/ServletRedirector</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>JspRedirector</servlet-name> <url-pattern>/JspRedirector</url-pattern> </servlet-mapping>
This configures the redirectors that need to field the HTTP requests from Cactus and execute the unit test in the container. Once you have saved the web.xml, you might need to restart the HTTP container if you have already started it. Otherwise, you can right-click on the test case and choose "Run->Run on Server". If you want to debug the component or the test you can choose "Debug->Debug on Server" instead. Assuming our test passes, we should get that wonderful JUnit "green bar" in the JUnit window.
Now we write a second test method to make sure the PermissionManager is denying requests if you only have the insufficient ApplicationPermission:
public void testPermissionManagerDisallow() throws Exception { try { //Make sure that the ApplicationPermission does not give test permission session.setAttribute(PermissionManager.TOKEN_KEY, new ApplicationPermission("Login")); PermissionManager manager = new PermissionManager(new TestPermission("Test")); manager.validate(request); } catch (InsufficientPermissionsException e) { } fail("Should not have allowed this request"); }Ooops--the red bar. It looks like our test failed. Looking at it, it is obvious why. If the InsufficientPermissionsException is being caught and execution is falling through to the fail() call. What we really meant was this:
public void testPermissionManagerDisallow() throws Exception { try { //Make sure that the ApplicationPermission does not give test permission session.setAttribute(PermissionManager.TOKEN_KEY, new ApplicationPermission("Login")); PermissionManager manager = new PermissionManager(new TestPermission("Test")); manager.validate(request); fail("Should not have allowed this request"); } catch (InsufficientPermissionsException e) { } }
Much better. Back to the green bar.
That's really all there is to it. All the normal JUnit best practices apply. You can aggregate tests with Suites and run Cactus tests and vanilla JUnit tests side-by-side. For more sophisticated Cactus test development, such as testing JSPs and Filters and using authentication, consult the Cactus website.The source code for this article is pretty contrived and you could easily come up with your own, but if you want it, the entire web project is available in this zip file.