9

I'm having a hard time trying to implement unit tests on my JSF backing bean classes... For instance some of the methods use session or request parameters, obtained using this sort of code:

FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("paramKey");.

My question is: how can I test a method which obtains values from the session or request?

Aritz
  • 30,971
  • 16
  • 136
  • 217
Felipe Reis
  • 479
  • 8
  • 24
  • What framework are you using to test your JSF managed beans? – Luiggi Mendoza Jul 28 '13 at 17:28
  • 1
    I'm trying to avoid having to test on a managed container (using Arquillian for instance). My goal is to mock all method calls to injected resources, for that I'm looking into [EasyGloss](https://java.net/projects/easygloss). If you have a better strategy, please let me know :-) – Felipe Reis Jul 28 '13 at 17:47

2 Answers2

9

What I usually do is to avoid calling static methods into the beans I want to test. That implies your current code to be refactored:

FacesContext.getCurrentInstance().getExternalContext()
    .getSessionMap().get("paramKey");

Are there ways to test them with their static method calls? Probably there are, but they led me to a lot of trouble more than help. So at the end I got rid of them and changed my design. Just let a second bean do it (which you'll mock later). In your case, create a @SessionScoped bean which manages that functionality:

@ManagedBean
@SessionScoped
public class SessionBean{

    public Object getSessionParam(String paramKey){
        FacesContext.getCurrentInstance().getExternalContext()
           .getSessionMap().get(paramKey);
    }

}

And inject that bean in every single bean that needs it (I usually extend my view/request beans from an abstract bean which has it, so don't have to implement it in every single bean):

@ManagedBean
@RequestScoped
public class RequestBean{

    @ManagedProperty(value="#{sessionBean}")
    private SessionBean sessionBean;

    public void accessSessionParam(){
        sessionBean.getSessionParam("name");
    }

}

That way you can access static methods easily, via your auxiliary SessionBean. Then, how to test it? Just create a mock of it (using Mockito, for instance):

public class Test{

    public void test1(){
        SessionBean sb = Mockito.mock(SessionBean.class);
        //Mock your 'getSessionParam' method
        ValueBean vb = new ValueBean();
        Mockito.when(sb.getSessionParam(Mockito.anyString()).thenReturn(vb);
        //Create your bean to test and set your mock to it
        RequestBean rb = new RequestBean();
        rb.setSessionBean(sb);
        //here you can test your RequestBean assuming 
        //sessionBean.getSessionParam() 
        //will return vb for every single call
    }

}
Aritz
  • 30,971
  • 16
  • 136
  • 217
  • Nice approach. Just one observation: The `getSessionParam` method didn't have to be in a ManagedBean, it could be in a POJO, right? Since it doesn't use any Bean specific resources... – Felipe Reis Jul 28 '13 at 23:00
  • The advantage of JSF managed beans is that the frameworks creates them once for the scope you specified. That means if you make it session scope it will be created only once per user session and automatically injected. You can make a normal bean and create it by hand everytime you want to use it, but that means wasting more resources. And, of course, this will work only while you're into a JSF scope. – Aritz Jul 29 '13 at 04:31
  • Also keep in mind you're using Dependency Injection letting the framework set your bean here. Remember that [DI makes easier the testing concept](http://stackoverflow.com/a/130862/1199132). – Aritz Jul 29 '13 at 05:48
6

It is possible to mock FacesContext but this is less than ideal. Mockito example:

import javax.faces.context.FacesContext;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public abstract class ContextMocker extends FacesContext {
  private ContextMocker() {}

  private static final Release RELEASE = new Release();

  private static class Release implements Answer<Void> {
    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
      setCurrentInstance(null);
      return null;
    }
  }

  public static FacesContext mockFacesContext() {
    FacesContext context = Mockito.mock(FacesContext.class);
    setCurrentInstance(context);
    Mockito.doAnswer(RELEASE)
        .when(context)
        .release();
    return context;
  }
}

If your platform supports it, prefer CDI to JSF managed beans. CDI has static dependency checking and injects proxies to prevent scope leak. CDI doesn't support all of JSF's features but it is relatively easy to connect JSF managed beans to CDI where necessary.

JSF managed beans restrict the scope that you inject types into but even that can lead to scope leaks. For example, the #{sessionScope} variable can be injected into a session scope bean even though the object belongs to the request scoped ExternalContext. It is possible to overcome this by writing your own JSF bean proxies.

Note: most of this was written with Java EE 6 in mind; things might have improved with Java EE 7.

McDowell
  • 107,573
  • 31
  • 204
  • 267