Testing Secure Pages
Most applications have at least some pages that require authenticated access. So you will need your test to log in to the application before you start testing the secure page. JSFUnit provides a way to easily log in when the JSFSession is started.
We do this using the Strategy pattern. You provide an implementation of InitialRequestStrategy. For applications that use JEE container-managed security, JSFUnit has built-in strategy classes for BASIC and FORM authentication.
Testing with BASIC authentication.
Here is an example using the BasicAuthenticationStrategy.
WebClientSpec wcSpec = new WebClientSpec("/mysecurepage.jsf"); wcSpec.setInitialRequestStrategy(new BasicAuthenticationStrategy("username", "password")); JSFSession jsfSession = new JSFSession(wcSpec); JSFClientSession client = jsfSession.getJSFClientSession(); JSFServerSession server = jsfSession.getJSFServerSession();
Testing with FORM authentication.
For form authentication, you use the FormAuthenticationStrategy. Besides user name and password, you will need to provide a reference to the submit button so that JSFUnit can automatically submit the form.
<form method="POST" action="j_security_check" name="loginform" id="loginform"> <tr> <td>username</td><td><input type="text" name="j_username"/></td> </tr> <tr> <td>password</td><td><input type="password" name="j_password"/></td> </tr> <tr><td><input type="submit" name="login_button" value="login"/></td></tr> </form>
WebClientSpec wcSpec = new WebClientSpec("/mysecurepage.jsf"); FormAuthenticationStrategy formAuth = new FormAuthenticationStrategy("username", "password", "login_button"); wcSpec.setInitialRequestStrategy(formAuth); JSFSession jsfSession = new JSFSession(wcSpec); JSFClientSession client = jsfSession.getJSFClientSession(); JSFServerSession server = jsfSession.getJSFServerSession();
Note that the reference to the submit button, "login_button", is the value of the "name" attribute and not the "id" attribute. Also note that the inputs for user and password must be "j_username" and "j_password" as per the servlet spec.
The FormAuthenticationStrategy can sometimes be used for non-standard logins that don't follow sevlet spec's naming rules. For this, you can use the alternate constructor:
public FormAuthenticationStrategy(String userName, String password, String submitComponent, String userNameComponent, String passwordComponent)
Testing non-JEE authentication
For any application that does not use container-managed BASIC or FORM authentication, you will need to write your own implementation of InitialRequestStrategy. The javadoc is here. Usually, you will just subclass SimpleInitialRequestStrategy and then use the HtmlUnit API to get you past whatever security is in place. The source code for FormAuthenticationStrategy serves as a good example:
public class FormAuthenticationStrategy extends SimpleInitialRequestStrategy { private String userName; private String password; private String submitComponent; public FormAuthenticationStrategy(String userName, String password, String submitComponent) { this.userName = userName; this.password = password; this.submitComponent = submitComponent; } /** * Perform the initial request and provide FORM authentication * credentials when challenged. * * @param wcSpec The WebClient specification. * * @return The requested page if authentication passed. Otherwise, return * the error page specified in web.xml. */ public Page doInitialRequest(WebClientSpec wcSpec) throws IOException { HtmlPage page = (HtmlPage)super.doInitialRequest(wcSpec); setValue(page, "j_username", this.userName); setValue(page, "j_password", this.password); return clickSubmitComponent(page); } protected Page clickSubmitComponent(HtmlPage page) throws IOException { HtmlElement htmlElement = getElement(page, this.submitComponent); return htmlElement.click(); } protected void setValue(HtmlPage page, String elementName, String value) { HtmlElement element = getElement(page, elementName); if (!(element instanceof HtmlInput)) { throw new IllegalStateException("Component with name=" + elementName + " is not an HtmlInput element."); } HtmlInput inputElement = (HtmlInput)element; inputElement.setValueAttribute(value); } protected HtmlElement getElement(HtmlPage page, String elementName) { List<HtmlElement> elements = page.getHtmlElementsByName(elementName); if (elements.size() == 0) { throw new IllegalStateException("Component with name=" + elementName + " was not found on the login page."); } if (elements.size() > 1) { throw new IllegalStateException("More than one component with name=" + elementName + " was found on the login page."); } return elements.get(0); }
Logging Out
The usual way to implement "log out" in JSF is to get the HttpSession from the JSF ExternalContext and call HttpSession.invalidate().
ExternalContext extCtx = FacesContext.getCurrentInstance().getExternalContext(); HttpSession httpSession = (HttpSession)extCtx.getSession(false); if (httpSession != null) httpSession.invalidate();
If you do not get the session via the ExternalContext then JSFUnit will fail. The reason is because you are testing inside the container and JSFUnit needs to keep the session alive as a conduit between JSF and your tests. During a JSFUnit test, HttpSession.invalidate() will not completely destroy the session. Rather, it will just remove all attributes not used by the JSFUnit framework.
Comments