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