Mock Objects for Test Driven JSF Development (org.jboss.test-jsf:jsf-mock project)

Introduction

  

     While Test Driven JSF Development allows to run unit tests in the real JSF environment, there are many cases when the usage of mock objects for testing is more efficient, because is easier to program and faster in tests execution. Mock objects are the objects that mimic the behavior of simulated object in a controlled way (as defined in Wikipedia). This article covers JSF Mock library (org.jboss.test-jsf:jsf-mock project) that can be used to create mock objects for all the main JSF entities. Also the library is fully compatible with JSF 2.0.

 

     Because most of JSF API objects are defined as abstract classes ( not interfaces ), it is hard to use Java Mock frameworks without extensions that able to modify Java classes. JSF Mock is based on the EasyMock library and very similar to its Class Extension subproject, but it uses mock classes generated at build time, and does not depend on any third party libraries. Therefore,  probability of dependencies conflict and performance impact are minimized. All functionality is encapsulated by methods in the independent "Environment" class and static methods, so it can be used with TestNg/Junit 4 POJO tests.

Environment setup

     Add the following dependencies to pom.xml (replace ${jsfmock.version} & ${easymock.version} with the version strings of JSF Mock & EasyMock respectively):
    <dependency>
      <groupId>org.jboss.test-jsf</groupId>
      <artifactId>jsf-mock</artifactId>
      <version>${jsfmock.version}</version>
      <scope>test</scope>
    </dependency>
 
     Any xUnit framework can be used to write tests, e.g. jUnit or TestNG.

Tests development

     EasyMock objects are programmed in the following steps:
  1. Mock objects are created
  2. Expectations are recorded
  3. Mock objects are switched to “replay” state
  4. Actions are executed on mock objects
  5. Expectations are verified for mock objects
     Now let's provide more details.

Mock objects creation

     To be able to create Mock objects, instance of MockFacesEnvironment should be obtained.
     Here is how to do that:
environment = MockFacesEnvironment.createEnvironment();
     Different types of environments can be requested, e.g.:
environment = MockFacesEnvironment.createStrictEnvironment();
NB: order checking is disabled by default in EasyMock.
     Also some preconfigured expectations for environment can be set when it's created ( most of methods return the same Environment object so they can be used in chain ):

 

environment = MockFacesEnvironment.createEnvironment().withExternalContext().withRenderKit();     
environment = MockFacesEnvironment.createEnvironment().withRenderKit();
see documentation/code for more available methods.
     To create mock for JSF object, call MockFacesEnvironment#createMock(Class<?> classToMock) for unnamed mocks or MockFacesEnvironment#createMock(String name, Class<?> classToMock) for named mocks.
     Examples:
environment = MockFacesEnvironment.createEnvironment();
component = environment.createMock(UIComponentBase.class);
responseWriter = environment.createMock(ResponseWriter.class);
     Here's a list of classes that can be mocked using JSF Mock out of the box:
FacesContext, FacesContextFactory, ExternalContext, ExternalContextFactory, Flash, ExceptionHandler, ExceptionHandlerFactory, PartialViewContext, PartialViewContextFactory, ResponseWriter, Application, ApplicationFactory, ViewHandler, NavigationHandler, StateManager, Resource, ResourceHandler, UIComponent, UIComponentBase, UIViewRoot, UIOutput, UIInput, UICommand, ClientBehaviorContext, VisitContext, VisitContextFactory, MethodBinding, ValueBinding, PropertyResolver, VariableResolver, Lifecycle, LifecycleFactory, ClientBehaviorRenderer, Renderer, RenderKit, RenderKitFactory, ResponseStateManager, ViewDeclarationLanguage, ViewDeclarationLanguageFactory, StateManagementStrategy, ValueExpression, MethodExpression, ExpressionFactory, ELContext.
If  the parameter class is not found among predefined classes, call is forwarded to the original EasyMock framework, so the same method can be used to create mock objects for any Java interface.
NB: some mock classes have predefined behaviors set like MockFacesEnvironment has; most useful are getChildren()/getFacets() defined in the mocks for UIComponentBase and its subclasses.
     Mocking for classes missing in this list can be set up for the project using special Maven goal (TBD description)
     FacesMock#createMock(...) & EasyMock#createMock(...) methods can be also used to create mock objects. However creating them via MockFacesEnvironment has the specifics that all mock objects created in such way share the same mock control object and this allows to verify/replay/reset all mock objects just by calling methods of MockFacesEnvironment class, e.g. MockFacesEnvironment#verify(). Also strict/nice/default type of mock is inherited from the type of MockFacesEnvironment.
Note that instance of MockFacesEnvironment provides ready to use mock objects for FacesContext, Application, etc. so they should not be created explicitly.

Recording expectations

     Expectations define how mock objects behave in response to some action:
  • what methods are called on Mock objects
  • how many times and in what order these methods are called
  • what method arguments are passed
  • what value is returned
  • what exceptions are expected
     Recording the expectations is as simple as calling expected methods on mock objects. More on expectations can be found in EasyMock documentation, so I'll just put in some very common examples.
IExpectationSetters interface
     Interface IExpectationSetters allows to configure how many times method is to be called (by default it's one and only one), return values, thrown exceptions, etc.
     Instance of IExpectationSetters can be obtained by wrapping call to method into EasyMock#expect() method:
componentAttributes = new HashMap<String, Object>();
IExpectationSetters<Map<String, Object>> expectationSetters = expect(component.getAttributes());
expectationSetters.andStubReturn(componentAttributes);
// More compact record:
expect(component.getAttributes()).andStubReturn(componentAttributes);

 

or by using EasyMock#expectLastCall() for methods with void return type:
responseWriter.writeText(eq(String.valueOf("test value".length())), EasyMock.<String>isNull());     
expectLastCall().anyTimes();
Stub methods

 

     These methods just return values, there's no check how many times these methods were called or if they were called at all. Typical usage is to assign necessary attributes to mock objects, like:
  • Component attributes map (NB: this is not fully compatible with the convention of UIComponent attributes map):
componentAttributes = new HashMap<String, Object>();
expect(component.getAttributes()).andStubReturn(componentAttributes);
  • ResponseWriter:

 

responseWriter = environment.createMock(ResponseWriter.class);
expect(environment.getFacesContext().getResponseWriter()).andStubReturn(responseWriter);
expect(responseWriter.getContentType()).andStubReturn("text/html")
     It's convenient to use stub methods in setUp()/@Before methods.
Argument matchers
     Argument matchers allow to check if methods arguments satisfy some condition. EasyMock declares vast set of matchers, like:
EasyMock.eq(...)
EasyMock.null()
EasyMock.notNull()
EasyMock.same(...) 
etc.
     Examples of matchers usage:
responseWriter.writeAttribute(eq("disabled"), eq(Boolean.TRUE), EasyMock.<String>isNull()); 
expect(component.getClientId(same(facesContext))).andStubReturn("formId:componentId"); 
     Example of fully-fledged setUp() method:
@Before
public void setUp() throws Exception {
     environment = MockFacesEnvironment.createStrictEnvironment();
     facesContext = environment.getFacesContext();
     component = environment.createMock(UIComponentBase.class);
 
     componentAttributes = new HashMap<String, Object>();
     expect(component.getAttributes()).andStubReturn(componentAttributes);
 
     expect(component.getClientId(same(environment.getFacesContext()))).andStubReturn(CLIENT_ID);
     
     responseWriter = environment.createMock(ResponseWriter.class);
     expect(environment.getFacesContext().getResponseWriter()).andStubReturn(responseWriter);
     expect(responseWriter.getContentType()).andStubReturn("text/html");
}

Switching to “replay” state

     After all expectations are set, mock objects should be switched to “replay” state, in order to notify mock objects that they should now execute expectations. This can be achieved either by calling MockFacesEnvironment#replay() if mock objects were created using MockFacesEnvironment#createMock(...), or EasyMock#replay(Object...)/FacesMock#replay(Object...).
     Example:
facesEnvironment.replay();

Actions are executed on mock objects

     This step involves execution of the functionality for which the expectations were recorded, e.g.:
RenderKitUtils.renderPassThroughAttributes(facesContext, component, knownAttributes);

Expectations verification

     In the end of the test verification of expectation must happen. This can be done either by calling MockFacesEnvironment#verify() if mock objects were created using MockFacesEnvironment#createMock(...), or EasyMock#verify(Object...)/FacesMock#verify(Object...). It is convenient to put call to these methods into tearDown()/@After method.
     Example:
facesEnvironment.verify();
     As part of cleanup, MockFacesEnvironment#release() should be called:
facesEnvironment.release();

 

Fully-fledged test example (JUnit 4)

 

     Basic renderer class which functionality we are testing:

 

package org.richfaces.cdk;

import java.io.IOException;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

public class FooRenderer extends Renderer {

    @Override
    public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
        super.encodeEnd(context, component);
        
        Map<String, Object> attributes = component.getAttributes();
        if (Boolean.TRUE.equals(attributes.get("shouldWriteValue"))) {
            ResponseWriter responseWriter = context.getResponseWriter();
            responseWriter.writeText(attributes.get("value"), null);
        }
    }
}

 

     Test itself:

package org.richfaces.cdk;

import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;

import java.util.HashMap;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.ResponseWriter;

import org.easymock.EasyMock;
import org.jboss.test.faces.mock.MockFacesEnvironment;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class FooTest {

    private static final String TEXT_FOR_TEST = "text for test";

    private MockFacesEnvironment environment;
    
    private UIComponent component;
    
    private ResponseWriter responseWriter;
    
    private Map<String, Object> attributesMap;
    
    @Before
    public void setUp() throws Exception {
        //create mock environment
        environment = MockFacesEnvironment.createEnvironment();

        //create mock component
        component = environment.createMock(UIComponentBase.class);
        
        //create mock response writer
        responseWriter = environment.createMock(ResponseWriter.class);
        expect(environment.getFacesContext().getResponseWriter()).andStubReturn(responseWriter);
        
        //set up attributes map
        attributesMap = new HashMap<String, Object>();
        expect(component.getAttributes()).andStubReturn(attributesMap);
    }
    
    @After
    public void tearDown() throws Exception {
        //do verification for mocked objects
        environment.verify();

        //do clean up
        environment.release();
        environment = null;
        
        component = null;
        responseWriter = null;
        attributesMap = null;
    }
    
    @Test
    public void testMockRenderer() throws Exception {
        //set attribute values for this test
        attributesMap.put("shouldWriteValue", Boolean.TRUE);
        attributesMap.put("value", TEXT_FOR_TEST);
        
        //record expectations
        responseWriter.writeText(eq(TEXT_FOR_TEST), EasyMock.<String>isNull());
        
        //switch to "replay" state
        environment.replay();

        //execute action
        FooRenderer fooRenderer = new FooRenderer();
        fooRenderer.encodeEnd(environment.getFacesContext(), component);
    }
}

Project information

  1. SVN: http://anonsvn.jboss.org/repos/test-utils/jboss-test-jsf