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
<dependency> <groupId>org.jboss.test-jsf</groupId> <artifactId>jsf-mock</artifactId> <version>${jsfmock.version}</version> <scope>test</scope> </dependency>
Tests development
- Mock objects are created
- Expectations are recorded
- Mock objects are switched to “replay” state
- Actions are executed on mock objects
- Expectations are verified for mock objects
Mock objects creation
environment = MockFacesEnvironment.createEnvironment();
environment = MockFacesEnvironment.createStrictEnvironment();
environment = MockFacesEnvironment.createEnvironment().withExternalContext().withRenderKit(); environment = MockFacesEnvironment.createEnvironment().withRenderKit();
environment = MockFacesEnvironment.createEnvironment(); component = environment.createMock(UIComponentBase.class); responseWriter = environment.createMock(ResponseWriter.class);
Recording expectations
- 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
IExpectationSetters interface
componentAttributes = new HashMap<String, Object>(); IExpectationSetters<Map<String, Object>> expectationSetters = expect(component.getAttributes()); expectationSetters.andStubReturn(componentAttributes); // More compact record: expect(component.getAttributes()).andStubReturn(componentAttributes);
responseWriter.writeText(eq(String.valueOf("test value".length())), EasyMock.<String>isNull()); expectLastCall().anyTimes();
Stub methods
- 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")
Argument matchers
EasyMock.eq(...) EasyMock.null() EasyMock.notNull() EasyMock.same(...)
responseWriter.writeAttribute(eq("disabled"), eq(Boolean.TRUE), EasyMock.<String>isNull()); expect(component.getClientId(same(facesContext))).andStubReturn("formId:componentId");
@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
facesEnvironment.replay();
Actions are executed on mock objects
RenderKitUtils.renderPassThroughAttributes(facesContext, component, knownAttributes);
Expectations verification
facesEnvironment.verify();
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); } }
Comments