Unit-testing Java Server Faces backing beans

THIS POST explains how to avoid the following error message when running tests covering a validate(…) throws ValidatorException method in JSF.

Absent Code attribute in method that is not native or abstract in class file javax/faces/validator/ValidatorException

From my cursory search, it appears that the test-runner expects actual implementations of ValidatorException, whereas compiled tests have access to the API [interfaces] only. There are many pointers to solutions along the line of “use an alternative JavaEE API that includes the implementation, e.g. Geronimo”.

However, basing the correctness of one’s code on specific implementations does more harm than good, and a better solution is to design classes in such a way that the code under test is isolated into an abstract class.

For example, say I have an

<f:viewParam id=”id” name=”id” value=”#{actionDetailsBean.id}”>

with a validator bound to method #{actionDetailsBean.validate}.

The proper approach to implement this is to decouple the validation code from JSF, such that you end up with a method delegated to by the bound validate(…) method.

The following code snippet shows how the validation method would be called.

@ManagedBean(name="actionDetailsBean")
@RequestScoped
public class ActionDetailsBean extends AbstractActionDetailsBean {

    private static final String COMPONENT_ID = "id";

    public ActionDetailsBean() {
        super();
    }

    public ActionDetailsBean(ActionDetailsBeanOut actionDetailsBeanOut) {
        super(actionDetailsBeanOut);
    }

    public void validate(FacesContext context, UIComponent component, Object value) throws ValidatorException{
        switch (component.getId().toLowerCase()) {
            case COMPONENT_ID:
                validateId();
                break;
        }
    }

    private void validateId() {
        load();
        if (!isLoaded())
            throw new ValidatorException(new FacesMessage("Id not recognised."));
    }
}

Here, the extracted method validateId() calls method load(). The method isLoaded() returns a boolean value to indicate whether the “action details” were loaded and, by extension, whether the id was valid.

Another tenet of unit-testing is that methods should be tested for their ability to fulfil their responsibility without exposing their implementations. In the present case, there is no need to test methods validate(…) and validateId(). We trust that validate(…) will get called by JSF and will throw a ValidatorException if a validation error occurs, which means that testing method load() is sufficient.

The following code extract shows the base class for ActionDetailsBean.

public abstract class AbstractActionDetailsBean {

    @Inject
    protected ActionDetailsBeanOut actionDetailsBeanOut;

    private String id;
    private String title;
    private String description;
    private String context;
    private boolean loaded;

    public AbstractActionDetailsBean() {
    }

    public AbstractActionDetailsBean(ActionDetailsBeanOut actionDetailsBeanOut) {
        this.actionDetailsBeanOut = actionDetailsBeanOut;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    protected boolean isLoaded() {
        return loaded;
    }

    public void load() {
        if (loaded) return;
        Map actionDetails = actionDetailsBeanOut.requestActionDetails(id);
        if (null == actionDetails) return;
        setId((String) actionDetails.get("id"));
        setTitle((String) actionDetails.get("title"));
        setDescription((String) actionDetails.get("description"));
        setContext((String) actionDetails.get("context"));
        loaded = true;
    }
}

ActionDetailsBeanOut is a Service Provider Interface (SPI), the implementation of which is injected at run-time to provide “action data”. load() populates the bean and sets loaded to true, if successful. AbstractActionDetailsBean has no dependency other than the injection.

Finally, coming back to the initial premise of this post, the way to avoid the dependency on a concrete JavaEE class and make your unit-test runnable is to test an anonymous implementation of AbstractActionDetailsBean, as follows.

public class AbstractActionDetailsBeanTest {

    @Test
    public void testLoad() {
        ActionDetailsBeanOut actionDetailsBeanOut = new ActionDetailsBeanOut() {
            @Override
            public Map requestActionDetails(String id) {
                HashMap details = new HashMap<>();
                details.put("id", "id");
                details.put("title", "title");
                details.put("description", "description");
                details.put("context", "@context");
                return details;
            }
        };
        AbstractActionDetailsBean actionDetailsBean = new AbstractActionDetailsBean(actionDetailsBeanOut) {};
        actionDetailsBean.setId("id");
        actionDetailsBean.load();
        Assert.assertEquals("id", actionDetailsBean.getId());
        Assert.assertEquals("title", actionDetailsBean.getTitle());
        Assert.assertEquals("description", actionDetailsBean.getDescription());
        Assert.assertEquals("@context", actionDetailsBean.getContext());
        Assert.assertTrue(actionDetailsBean.isLoaded());
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>