- Old approach: Utilization of Mockito and JSF / Servlet mocks from "dead" Apache Shale, Spring, ebtam FacesContext.
- Disadvantages: Limitations, no mocking of static, final, private classes / methods. Mix of different mocks causes an unconsistent environment. Hacks for the production code.
- New approach: Utilization of PowerMock and MyFaces Test frameworks.
- Advantages: Mocking of static, final, private classes / methods and more is possible. Unified mock objects and consistent weaving for all fundamental JSF / Servlet pendants.
- JavaServer Faces
- Servlet
package com.xyz.webapp.basemock; import org.apache.myfaces.test.base.junit4.AbstractJsfTestCase; import org.apache.myfaces.test.mock.*; import org.apache.myfaces.test.mock.lifecycle.MockLifecycle; public class JsfServletMock extends AbstractJsfTestCase { public MockApplication20 mockApplication; public MockExternalContext20 mockExternalContext; public MockFacesContext20 mockFacesContext; public MockLifecycle mockLifecycle; public MockServletContext mockServletContext; public MockServletConfig mockServletConfig; public MockHttpSession mockHttpSession; public MockHttpServletRequest mockHttpServletRequest; public MockHttpServletResponse mockHttpServletResponse; public JsfServletMock() { try { super.setUp(); } catch (Exception e) { throw new IllegalStateException("JSF / Servlet mocks could not be initialized"); } // JSF API mockApplication = (MockApplication20) application; mockExternalContext = (MockExternalContext20) externalContext; mockFacesContext = (MockFacesContext20) facesContext; mockLifecycle = lifecycle; // Servlet API mockServletContext = servletContext; mockServletConfig = config; mockHttpSession = session; mockHttpServletRequest = request; mockHttpServletResponse = response; } }An object of this class can be used in tests per delegation pattern. Define the PowerMockRunner with @RunWith at the beginning. Define classes which static, final, private methods will be mocked / tested with @PrepareForTest. Take an example.
import com.xyz.webapp.basemock.JsfServletMock; import com.xyz.webapp.util.FacesUtils; import com.xyz.webapp.util.Observer; import com.xyz.webapp.util.ObserverUtil; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.*; @RunWith(PowerMockRunner.class) @PrepareForTest({FacesUtils.class, ObserverUtil.class}) public class DataExportWorkflowPageTest { private JsfServletMock jsfServletMock; @Mock private StatusProfileFinder statusProfileFinder; @Mock private Collection<Observer> observers; private DataExportWorkflowPage dewp = new DataExportWorkflowPage(); @Before public void before() { jsfServletMock = new JsfServletMock(); dewp.setObservers(observers); dewp.setStatusProfileFinder(statusProfileFinder); } @Test public void initialize() { mockStatic(FacesUtils.class); DataExportHistoryForm dehf = mock(DataExportHistoryForm.class); DataExportContentForm decf = mock(DataExportContentForm.class); WorkflowData workflowData = mock(WorkflowData.class); when(FacesUtils.accessManagedBean("dataExportHistoryForm")).thenReturn(dehf); when(FacesUtils.accessManagedBean("dataExportContentForm")).thenReturn(decf); List<StatusProfile> spList = new ArrayList<StatusProfile>(); StatusProfile sp1 = new StatusProfile(GenericObjectType.DOCUMENT); sp1.setBusinessKey("document"); spList.add(sp1); StatusProfile sp2 = new StatusProfile(GenericObjectType.ACTIVITY); sp2.setBusinessKey("activity"); spList.add(sp2); StatusProfile sp3 = new StatusProfile(GenericObjectType.EXPENSE_DOCUMENT_ITEM); sp3.setBusinessKey("expense"); spList.add(sp3); jsfServletMock.mockHttpSession.setAttribute(Constants.SESSION_CLIENT, new Client()); when(statusProfileFinder.getAllLimitBy(any(Client.class), eq(Constants.MAX_RESULTS_DEFAULT))).thenReturn(spList); when(FacesUtils.accessManagedBean("workflowData")).thenReturn(workflowData); dewp.initialize(); verify(observers).add(dehf); verify(observers).add(decf); verifyNoMoreInteractions(observers); assertEquals(GenericObjectType.DOCUMENT.getId(), dewp.getStatusProfile()); assertEquals(workflowData, dewp.getWorkflowData()); } ... }After JsfServletMock instantiation in a method annotated with @Before we have a full access to mock objects. E.g. MockHttpSession can be accessed by jsfServletMock.mockHttpSession. In the example I want to mock the static method FacesUtils.accessManagedBean(String) in the static class. We have to write first
mockStatic(FacesUtils.class);to achieve that. And then quite normally
when(FacesUtils.accessManagedBean(...)).thenReturn(...);In another small example I use the call
verifyStatic();to verify if the subsequent static call got really called. In the example below we verify if
ObserverUtil.notifyAll(depd);was called within
depd.setProcedureStep2(null);
@RunWith(PowerMockRunner.class) @PrepareForTest(ObserverUtil.class) public class DataExportProcedureDataTest { private DataExportProcedureData depd; @Mock private Collection<Observer> observers; @Before public void before() { depd = new DataExportProcedureData(); depd.setObservers(observers); } @Test public void setProcedureStep2() { mockStatic(ObserverUtil.class); depd.setProcedureStep2(null); verifyStatic(); ObserverUtil.notifyAll(depd); } ... }You see we can do both - mock static methods and verify behaviors.
Bring power to JSF tests! No need for workarounds any more! Keep your production code clean from the test driven stuff!