Introduction
Testing has become an important aspect for every application and an application cannot be released unless it is not thoroughly tested. JSFUnit provides an attempt to bring in testing capabilities for JSF applications. Not many frameworks exists in the market for testing JSF applications and this framework which originated from JBoss community provides wider coverage for testing JSF applications with respect to the managed beans state, navigation flows, application configuration etc. This article is an attempt to provide an introduction to the framework JSFUnit.
If you are not familiar with Java Server Faces, first read our list of articles on Java Server Faces (JSF) including Introduction to Java Server Faces by Raja. We have also listed some of the popular JSF Books.
JSFUnit Starter
In this section, we will see how to setup JSFUnit framework for testing JSF applications. We will also see how to write simple JSF test cases at the end of the section. The example is as simple as it will display a JSP page that will return static html content to the browser.
JSF Page
Given below is the code snippet of the JSF page. Note that for each component we have explicitly assigned identifiers through the id attribute. This is absolutely necessary because later on the test case access will try to get a reference to a JSF page component only through identifiers.
index.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <HTML> <f:view> <h1><h:outputText value="JSFUnit HelloJSF Demo Application" id="title"/></h1> <h:form id="startupForm"> <h:outputText value="JSF application is running" id="test"/> </h:form> </f:view> </HTML>
The above JSF page makes use of two outputText tags for displaying static content one being embedded within the form whereas the other didn’t.
Web Application Deployment Descriptor
In the below web application’s deployment description, apart from the regular entries related to JSF mapping, we have configured plenty of configurations related to JSFUnit suitable for performing testing on JSF pages.
web.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <filter> <filter-name>JSFUnitFilter</filter-name> <filter-class>org.jboss.jsfunit.framework.JSFUnitFilter</filter-class> </filter> <filter-mapping> <filter-name>JSFUnitFilter</filter-name> <servlet-name>ServletTestRunner</servlet-name> </filter-mapping> <filter-mapping> <filter-name>JSFUnitFilter</filter-name> <servlet-name>ServletRedirector</servlet-name> </filter-mapping> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> </servlet> <servlet> <servlet-name>ServletRedirector</servlet-name> <servlet-class>org.jboss.jsfunit.framework.JSFUnitServletRedirector</servlet-class> </servlet> <servlet> <servlet-name>ServletTestRunner</servlet-name> <servlet-class>org.apache.cactus.server.runner.ServletTestRunner</servlet-class> </servlet> <servlet-mapping> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletRedirector</servlet-name> <url-pattern>/ServletRedirector</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ServletTestRunner</servlet-name> <url-pattern>/ServletTestRunner</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
JSFUnit framework provides a facility for running the unit test cases for JSF application directly from the browser. To make use of it, we have to define a JSFUnit Filter represented through org.jboss..JSFUnitFilter which is used to initiate the environment suitable for running test cases. The JSF Servlet Redirector represented through org.jboss..JSFUnitServletRedirector is used for invoking test cases for JSF as well as does the job of clean up. As the name suggests, the component org.apache.cactus..ServletTestRunner acts as a Servlet for starting a JUnit Runner within a web application.
Vendor Specific Web Deployment Descriptor
This example is targeted to be deployed in Glassfish and the below vendor specific configuration file is used to define the context root for the web application as ‘JSFUnit-Startup’.
sun-web.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd"> <sun-web-app error-url=""> <context-root>/JSFUnit-Startup</context-root> </sun-web-app>
Faces Configuration
In this example application, there is no need to define managed beans or the navigation rules, so the faces configuration file will be empty.
faces-config.xml
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd"> <faces-config> </faces-config>
JSF Test Case
In this example, we will see how to write simple test cases for the above JSF page that we have written. Note that the following compile-time and run-time jar files need to be present in the classpath.
- ant-1.5.4.jar
- aspectjrt-1.2.1.jar
- cactus-13-1.7.1.jar
- cactus-ant-13-1.7.1.jar
- cargo-0.5.jar
- commons-codec-1.3.jar
- commons-collections-3.2.jar
- commons-httpclient-3.1.jar
- commons-io-1.4.jar
- commons-lang-2.4.jar
- commons-logging-1.1.1.jar
- cssparser-0.9.5.jar
- htmlunit-2.4.jar
- htmlunit-core-js-2.4.jar
- jboss-jsfunit-core-1.0.0.GA-SNAPSHOT.jar
- junit-3.8.1.jar
- nekohtml-1.9.9.jar
- sac-1.3.jar
- xalan-2.7.0.jar
- xercesImpl-2.8.1.jar
- xml-apis-1.0.b2.jar
JSFUnitStartupTest.java
package net.javabeat.jsfunit.articles.startup; import java.io.IOException; import javax.faces.component.UIComponent; import javax.faces.component.html.HtmlForm; import javax.faces.component.html.HtmlOutputText; import junit.framework.Test; import junit.framework.TestSuite; import org.apache.cactus.ServletTestCase; import org.jboss.jsfunit.jsfsession.JSFClientSession; import org.jboss.jsfunit.jsfsession.JSFServerSession; import org.jboss.jsfunit.jsfsession.JSFSession; public class JSFUnitStartupTest extends ServletTestCase{ private JSFClientSession client; private JSFServerSession server; @Override public void setUp() throws IOException{ JSFSession jsfSession = new JSFSession("/index.faces"); client = jsfSession.getJSFClientSession(); server = jsfSession.getJSFServerSession(); } public static Test suite(){ return new TestSuite( JSFUnitStartupTest.class ); } public void testGetCurrentViewId() throws IOException{ assertEquals("/index.jsp", server.getCurrentViewID()); } public void testTitleComponent() throws IOException{ UIComponent titleComponent = server.findComponent("title"); assertNotNull(titleComponent); Object componentValue = server.getComponentValue("title"); assertNotNull(componentValue); if (componentValue instanceof String){ String strValue = (String)componentValue; assertEquals("JSFUnit HelloJSF Demo Application", strValue); }else{ assertTrue("Value of title component is not an instance of String", false); } } public void testFormComponent() throws IOException{ UIComponent formComponent = server.findComponent("startupForm"); assertNotNull(formComponent); assertTrue(formComponent instanceof HtmlForm); UIComponent textComponent = server.findComponent("startupForm:test"); assertNotNull(textComponent); assertTrue(textComponent instanceof HtmlOutputText); Object componentValue = server.getComponentValue("startupForm:test"); assertNotNull(componentValue); if (componentValue instanceof String){ String strValue = (String)componentValue; assertEquals("JSF application is running", strValue); }else{ assertTrue("Value of test component is not an instance of String", false); } } }
The very first thing to note is all the test case classes should extend org.apache.cactus.ServetTestCase so that the Servlet Runner can run the test case class within a web application. In the setup() method we have tried to establish a session to a JSF page through JSFSession API. Through this JSFSession, one can have a client side session as well as a server side session by calling the methods getJSFClientSession() and getJSFServerSession(). Note that the argument we have given is the “/index.faces” and we have configured the web application’s deployment descriptor to map this to “/index.jsp”.
The test method testGetCurrentViewId() is used to test the identifier of the current view. Note that even though we have established a session to “/index.faces”, the final view displayed will be “/index.jsp” and we have asserted whether the view identifier is correct. In the example JSF page, we have displayed two label components with the help of outputText tag, one within the form and one outside the form tag. The test method testTitleComponent() is used to test whether the component is rendered and if rendered making sure the text displayed in the component is correct. One can use the method findComponent() method to identify a component displayed in the page. If the framework is able to find the component it will return a valid UIComponent reference and the value for the component can be obtained by calling getComponentValue(). Note that the test method makes appropriate assertions before checking for the existence and the value rendered in the component.
For identifying components within a form, the identifier “formId:componentId” can be used. For example if the form id is “testFormId” and the id of a component displayed in a form is “testComponentId”, then for identifying this component “testFormId:testComponentId” should be passed to the method findComponent() for proper identification and the third test case method just does that.
Running the Application
In this section, we will see how to run the JSF application and also how to invoke the test cases that test the page integrity from the browser.
Running the JSF application is simple. Assuming that the application is running locally on the port number 8080 and with the given context root ‘JSFUnit-Startup’, then the URL “http://localhost:8080/JSFUnit-Startup/index.faces” can be used to access the JSF page.
For running the test-cases and to see the test results on the browser, we have to make use of Servlet Test Case runner. The Servlet Test Case Runner class expects the test case class name in the parameter ‘suite’ and the xs file name for formatting the test results in the parameter ‘xsl’. Note that the xsl file that is used for formatting the test results is included in the source code in the name ‘cactus-report.xsl’
The combined query string now becomes “ServletTestRunner?suite=net.javabeat.jsfunit.articles.startup.JSFUnitStartupTest&xsl=cactus-report.xsl”. This query string has to be appended with the application’s context root, in our case, the application’s context root is http://localhost:8080/JSFUnit-Startup. So the URL “http://localhost:8080/JSFUnit-Startup/ServletTestRunner?suite=net.javabeat.jsfunit.articles.startup.JSFUnitStartupTest&xsl=cactus-report.xsl” can be used to run the test cases and the results will get displayed in the browser. The following screen shot captures the test results,
Test results for the jsf unit startup application.
Exploring JSFUnit API
In this example we will try to explore the client side API that JSFUnit framework supports. For illustrating this, we will develop a traditional login application which prompts for the username and a password during application startup. If the username and the password combination is correct, the application redirects to a successful page and when the password is incorrect, an error page will be returned. To make things interesting, we will redirect to a different page if the username is invaid.
Login JSF Page
Below is the login JSF page. This page displays the input components for accepting the username and password through the inputText and the inputSecret tags. Note that to validate the user information, when the login button is clicked, the control is forwarded to User managed bean’s login method which will be discussed in the forth coming section. Note that we have explicitly specified the identifier to each of the components.
login.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <HTML> <f:view> <h1><h:outputText value="Enter username and pasword" id="infoText"/></h1> <h:form id="startupForm"> <h:outputText id = "usernameLabel" value = "Username" /> <h:inputText id = "usernameText" value ="#{user.username}"/> <br/> <h:outputText id = "passwordLabel" value = "Password" /> <h:inputSecret id = "passwordText" value ="#{user.password}"/> <br/><br/> <h:commandButton id = "loginButton" action = "#{user.login}" value = "Login"/> </h:form> </f:view> </HTML>
Welcome JSF Page
Given below is the welcome page that will be displayed to the user when the username and the password are correct.
welcome.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <f:loadBundle basename = "net.javabeat.articles.jsfunit.login.messages" var = "message"/> <HTML> <f:view> <h1><h:outputText value="Welcome #{user.username}." id="infoText"/></h1> </f:view> </HTML>
Invalid User JSF Page
Have a look at the following invalid user JSF page.
invalid-user.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <HTML> <f:view> <h1><h:outputText value="Invalid user #{user.username}. Please try again." id="infoText"/></h1> </f:view> </HTML>
The above page will be displayed when the username is invalid. Note that for testing purpose, we have hard-coded the username as “admin”.
Error page
This page will be displayed when the password is incorrect for a valid username. For testing purposes, we have hard-coded the username and password to “admin” and “admin”.
error.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <HTML> <f:view> <h1><h:outputText value="Invalid password for the username #{user.username}. Please try again." id="infoText"/></h1> </f:view> </HTML>
User Managed bean
The following class which represents the managed bean for the login application does two things. At first, it encapsulates the username and password properties. Secondly, the validation of username and password is done within the implementation of this class in the method login().
User.java
package net.javabeat.articles.jsfunit.login; public class User { private String username; private String password; public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String login(){ if (username.equals("admin")){ if (password.equals("admin")){ return "welcome"; }else{ return "invalid-password"; } }else{ return "error"; } } }
Note that as part of validation in the login() method, if the user specifies correct username and password, a return value “welcome” is returned. Later on we will see how this return value maps to the view welcome.jsp. Similarly for the incorrect username and incorrect password, the views returned are error.jsp and invalid-password.jsp
Faces Configuration
In the below faces configuration file, we have configured the class User as a managed bean and have given the scope as session. Note that the return value view mapping during login process is modeled as navigation rules and navigation cases.
faces-config.xml
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.1//EN" "http://java.sun.com/dtd/web-facesconfig_1_1.dtd"> <faces-config> <managed-bean> <managed-bean-name>user</managed-bean-name> <managed-bean-class>net.javabeat.articles.jsfunit.login.User</managed-bean-class> <managed-bean-scope>session</managed-bean-scope> </managed-bean> <navigation-rule> <from-view-id>/login.jsp</from-view-id> <navigation-case> <from-outcome>welcome</from-outcome> <to-view-id>/welcome.jsp</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <from-view-id>/login.jsp</from-view-id> <navigation-case> <from-outcome>error</from-outcome> <to-view-id>/error.jsp</to-view-id> </navigation-case> </navigation-rule> <navigation-rule> <from-view-id>/login.jsp</from-view-id> <navigation-case> <from-outcome>invalid-password</from-outcome> <to-view-id>/invalid-password.jsp</to-view-id> </navigation-case> </navigation-rule> </faces-config>
JSF Test Cases
In the following test class, we will discuss three testing scenarios, one when the username and password given by the user is correct, the second being the scenario when the username is correct and the password is wrong, and finally for the username being incorrect. As part of the discussion we will also be seeing the usage of the class JSFClientSession.
LoginAppTest.java
package net.javabeat.articles.jsfunit.login; import java.io.IOException; import javax.faces.component.UIComponent; import junit.framework.Test; import junit.framework.TestSuite; import org.apache.cactus.ServletTestCase; import org.jboss.jsfunit.jsfsession.JSFClientSession; import org.jboss.jsfunit.jsfsession.JSFServerSession; import org.jboss.jsfunit.jsfsession.JSFSession; public class LoginAppTest extends ServletTestCase{ private JSFClientSession client; private JSFServerSession server; @Override public void setUp() throws IOException{ JSFSession jsfSession = new JSFSession("/login.faces"); client = jsfSession.getJSFClientSession(); server = jsfSession.getJSFServerSession(); } public static Test suite(){ return new TestSuite( LoginAppTest.class ); } public void testCorrectUsernameAndPassword() throws IOException{ UIComponent buttonComponent = server.findComponent("loginButton"); assertNotNull(buttonComponent); String username = "admin"; client.setValue("usernameText", username); client.setValue("passwordText", "admin"); client.click("loginButton"); String pageText = client.getPageAsText(); assertNotNull(pageText); assertTrue(pageText.contains("Welcome " + username + ".")); } public void testCorrectUsernameAndIncorrectPassword() throws IOException{ UIComponent buttonComponent = server.findComponent("loginButton"); assertNotNull(buttonComponent); String username = "admin"; client.setValue("usernameText", username); client.setValue("passwordText", "1234"); client.click("loginButton"); String pageText = client.getPageAsText(); assertNotNull(pageText); assertTrue(pageText.contains("Invalid password for the username " + username + ". Please try again.")); } public void testIncorrectUsername() throws IOException{ UIComponent buttonComponent = server.findComponent("loginButton"); assertNotNull(buttonComponent); String username = "admin123"; client.setValue("usernameText", username); client.setValue("passwordText", "admin"); client.click("loginButton"); String pageText = client.getPageAsText(); assertNotNull(pageText); assertTrue(pageText.contains("Invalid user " + username + ". Please try again.")); } }
As part of the regular process, the setup() method is overridden for acquiring a session to the application’s startup page – which is “/login.faces”. Now the application is displaying the login page and in the first test case, we have made of the JSFClientSession API for setting the username and password parameters by calling setValue(paramName, paramValue). Note that this will simulate as if the user is typing the values for the username and the password fields. Finally the request submission is simulated by calling the method click() defined on the JSFClientSession object. Note that argument passed to click() is the identifier for the button. As soon as the request is submitted and the response is received, the client can call the method getPageAsText() which will return a raw HTML page text. And finally the page content is validated for correctness by comparing it with the return value from getPageAsText() method. Similarly the second and the third test cases deals with incorrect username and incorrect password combination.
Running the application
The application can be invoked through the URL “http://localhost:8080/JSFUnit-Login/login.faces”.
And for invoking the test cases, the URL “http://localhost:8080/JSFUnit-Login/ServletTestRunner?suite=net.javabeat.articles.jsfunit.login.LoginAppTest&xsl=cactus-report.xsl”. Given below is the screen shot of the test results for the login application,
Test results for the jsf unit login application.
Miscellaneous
In this miscellaneous section, we will further dig into the API support on JSFUnit framework.
Adding Request Listener
A Request Listener object can be used to intercept client request and customizations can be injected before and after the invocation of client request. For example, the following code adds a simple Request Listener,
public void testRequestListener(){ WebClientSpec specification = new WebClientSpec("/login.faces"); WebConnection webConnection = specification.getWebClient().getWebConnection(); if (webConnection instanceof JSFUnitWebConnection){ JSFUnitWebConnection jsfConnection = (JSFUnitWebConnection)webConnection; MyRequestListener listener = new MyRequestListener(); jsfConnection.addListener(listener); } } class MyRequestListener implements RequestListener{ public void beforeRequest(WebRequestSettings settings) { System.out.println("Before Request"); throw new UnsupportedOperationException("Not supported yet."); } public void afterRequest(WebResponse settings) { System.out.println("After Request"); } }
Testing Faces Messages
An application can emit faces messages which can be warning, information or an error. JSFUnit framework provides support for iterating over the faces messages upon response completion for validating its contents. For example,
public void testGetFacesMessages(){ Iterator iterator = server.getFacesMessages("someComponentId"); while (iterator.hasNext()){ FacesMessage facesMessage = iterator.next(); // Validate the facesMessage object. } }
Testing Managed Bean states
The state of managed beans will keep changing in an application’s workflow and the framework adds capabilities for testing the value of a managed bean’s state giving an EL expression. Consider the following code snippet the gets the state of username and password for the Managed bean User.
public void testManagedBeans(){ String username = (String)server.getManagedBeanValue("${user.username}"); assertEquals("admin", username); String password = (String)server.getManagedBeanValue("${user.password}"); assertEquals("admin", password); }
Dumping Client Ids
Sometimes, as part of debugging, it will be essential for dumping the client id information pertaining the current page.
public void testClientId(){ ClientIDs clienIds = server.getClientIDs(); clienIds.dumpAllIDs(); }
The method dumpAllIDs() will print all the Client IDs to the console.
Conclusion
This article provided step by step process in setting up the environment for facilitating testing for JSF applications. Detailed discussions were given with respect to configuring and running test cases within a web application. The article also focused on explaining the essential APIs like JSFSession, JSFServerSession, JSFClientSession. Finally the article provided the miscellaneous capabitiies like checking the state of managed beans, adding request listeners, testing faces messages etc.