Wednesday, October 27, 2010

Tests with PowerMock and MyFaces Test frameworks

In one of projects we use Mockito as test framework. It's a great framework having some limitations. We also use different mock objects for JSF environment which come from various sources. A not comfortable mishmash. I have decided to introduce PowerMock and MyFaces Test frameworks. This changing was very effective. First I want to compare two test approaches:
  • 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.
PowerMock extends Mockito by using a custom classloader and bytecode manipulation. It enables mocking of static methods, constructors, final classes and methods, private methods and more. MyFaces Test Framework provides mock object libraries and base classes for creating own test cases. Mocks for the following APIs are provided:
  • JavaServer Faces
  • Servlet
The mock netting is as real. Created FacesContext instance will have been registered in the appropriate thread local variable, to simulate what a servlet container would do. The following section shows how to write power JSF 2 tests. For easing use of the new approach there is a class JsfServletMock which provides an access to JSF 2 and Servlet mocks.
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!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.