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