Blog

Monday, 28 May 2012

Mock FacesContext in Arquillian

Recently I wanted to test my backing beans for JSF views. Unfortunately the EntityQuery (from seam3-persistence-framework) is very tightly bound to JSF api, particularly it depends on FacesContext.getCurrentInstance().getELContext(). The problem is that the tests are not associated with JSF requests, so FacesContext.getCurrentInstance() returns null.

I decided to extend mock-contexts-extension to help mocking FacesContext.
Let's say that we want to test if our component for retrieving list of objects from database returns good results. The component extends class pl.com.it_crowd.seam.framework.EntityQuery which in turn extends pl.com.it_crowd.seam.framework.Query. Our component uses such EJBQL:
"select u from User u where u.company=#{currentUser.company}". Our EntityQuery will parse it and try to create ValueExpression,

    private List<Object> getParameterValues(List<ValueExpression> valueBindings)
    {
        List<Object> values = new ArrayList<Object>(valueBindings.size());
        for (int i = 0; i < valueBindings.size(); i++) {
            values.add(valueBindings.get(i).getValue(facesContext.get().getELContext()));
        }
        return values;
    }
As you can see it needs to get ELContext and it takes it from injected facesFontext instance. The problem is that call to facesContext.get() will result in exception, because FacesContext instance producer checks if FacessContext.getCurrentInstance returns null.
Setting FacesContext in regular way requires too many dependencies (ServletContext,ServletRequest,ServletResponse,Lifecycle) that I decided it's better to use mock. Especially that I need to mock only one method: getELContext().

Here is the solution: x
@RunWith(Arquillian.class)
public class UserListCIT {

    @Inject
    private CommonCITSteps commonCITSteps;

    @Inject
    private UserList userList;

    @SuppressWarnings("CdiInjectionPointsInspection")
    @Inject
    private ELContext elContext;

    @Inject
    private Identity identity;

    @Deployment
    public static WebArchive createDeployment()
    {
        //...standard archive building
    }

    @FacesContextRequired
    @ConversationScopeRequired
    @Test
    public void attemptToAccessOtherCompanyData()
    {
        Assert.assertFalse(identity.isLoggedIn());
        commonCITSteps.login("jack", "aaaaa");
        Assert.assertTrue(identity.isLoggedIn());
        Assert.assertTrue(userList.getResultList().isEmpty());
    }

    @MockFacesContextProducer
    public FacesContext mockFacesContext()
    {
        FacesContext mock = mock(FacesContext.class);
        when(mock.getELContext()).thenReturn(elContext);
        return mock;
    }
}
I annotate test method with @FacesContextRequired, which tells our mock-contexts-extension that test depends on FacesContext and the test provides mock for this. Also test class has method annotated with @MockFacesContextProducer which should return FacesContext. I use Mockito to create mock that returns elContext which is injected component, produced by seam solder.

If 2 different tests would need different mocks then both annotations FacesContextRequired and MockFacesContextProducer have "name" attribute which can be used to identify the right mock.

Read this article to learn about mock-contexts-extension.