Assigning Component ID's
The first thing to know about writing a JSFUnit test is that you should get into the habit of assigning component ID's to your components on a JSF page. For every JSF component (except <f:view>), you have the option of providing a component ID. If you don't provide one, JSF will create one for you. However, if you let JSF create the ID, you will have a hard time referencing the component in your tests. So you at least need to use an ID for any component that you might want to reference in a test.
Here is a simple example:
<h:inputText value="#{foo.text}" id="input_foo_text"></h:inputText>
In your test, you will be able to refer to this component as "input_foo_text".
JSFClientSession clientSession = new JSFClientSession("/index.jsf"); clientSession.setParameter("input_foo_text", "my value");
And that's all you need to know most of the time. Any time you want to use a method in JSFUnit that takes a "componentID" as a parameter, just use the one you assigned on your JSP or Facelets page.
But if you want to know a little more about what's going on underneath the covers, read below.
Rules for Component ID's
The JSF specification says that component IDs
Can not be a zero-length String.
The first character has to be a letter or an underscore ('_').
Subsequent characters must be a letter, a digit, an underscore ('_'), or a dash ('-')
How JSF Turns Component ID's into Client ID's
If you study JSF closely, you will notice that when a page is rendered as HTML each component takes on its component ID prefixed with its parent's component ID and a colon. This is known as the client ID. So given this JSF page:
<f:view> <h:form id="form1"> <h:outputText value="Enter your name:" id="prompt"></h:outputText> <h:inputText value="Stan" id="input_name"></h:inputText> <h:commandButton value="Submit" action="/index.jsp"></h:commandButton> </h:form> </f:view>
It will be rendered in HTML like this:
<form id="form1" name="form1" method="post" action="/foo/hello.jsf"> <span id="form1:prompt">Enter your name:</span> <input id="form1:input_name" type="text" name="form1:input_name" value="Stan" /> <input type="submit" name="form1:j_id_jsp_414981488_4" value="Submit" /> </form>
The Component ID for the <h:inputText> component is input_name. But the Client ID is form1:input_name. Note that because I didn't assign an ID to the <h:commandButton>, JSF automatically assigned a Component ID of j_id_jsp_414981488_4 and a Client ID of form1:j_id_jsp_414981488_4.
How JSFUnit resolves Component ID's and Client ID's
The JSFUnit API only requires that you know about the component ID you assigned on your JSF page. But since JSFUnit is using HtmlUnit to set parameters and submit forms, it needs to refer to HTML elements using the full client ID. So on the client side, JSFUnit does a substring match to find which component ID matches the rendered Client ID. This is done using an XPath query.
On the server side, JSFUnit has access to the component tree through the FacesContext. So for any given component ID, JSFUnit can search the component tree and do the same kind of substring match.
DuplicateClientIDException
Consider this case with two forms on a page (form1 and form2):
<f:view> <h:form id="form1"> <h:outputText value="Enter your name:" id="prompt"></h:outputText> <h:inputText value="Stan" id="input_name"></h:inputText> <h:commandButton value="Submit" action="/index.jsp"></h:commandButton> </h:form> <h:form id="form2"> <h:outputText value="Enter your name:" id="prompt"></h:outputText> <h:inputText value="Stan" id="input_name"></h:inputText> <h:commandButton value="Submit" action="/index.jsp"></h:commandButton> </h:form> </f:view>
The HTML will have two components that match the client ID for "input_name". If you pass "input_name" to the JSFUnit API you will get DuplicateClientIDException: 'input_name' matches more than one JSF component ID. Use a more specific ID suffix. Suffix matches: form1:input_name, form2:input_name.
In this case, you would just need to change your JSFUnit test to say something like:
client.setValue("form2:input_name", "Brian");
Client IDs for components that use collections
If you are using a component that is backed by a collection, then an index will be part of the client ID. For example:
<h:form id="form1"> <h:dataTable var="marathon" value="#{marathons.list}"> <h:column> <h:commandLink id="marathonSelect" onclick="var foo='bar';" action="#{marathons.select}"> <h:outputText id="selectText" value="Select"></h:outputText> </h:commandLink> </h:column> </h:dataTable> </h:form>
The resulting client IDs for the <h:commandLink> would be something like:
form1:_idJsp0:0:marathonSelect
form1:_idJsp0:1:marathonSelect
form1:_idJsp0:2:marathonSelect
To refer to the second element, your JSFUnit code would look something like this:
client.click("1:marathonSelect");
AJAX and FireBug
Sometimes, it can be hard to determine the correct client ID for AJAX components. A great tool is the FireBug plugin for FireFox. This tool lets you view the HTML is such a way that it is easy to determine which component is the real target of a 'click'.
Comments