Statement apply(Statement base, Description description)The first Statement parameter is a specific object which reprensents the method under the test from your test class. Such a test method can be invoked by base.evaluate(). You can place any custom code before and after the call base.evaluate(). A typically implementation follows this pattern
public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { // do something before invoking the method to be tested ... try { base.evaluate(); } finally { // do something after invoking the method to be tested ... } } }; }In short words: the apply method allows to intercept the base call of every test method and put a custom code around. Your TestRule implementation, say MyRule, can be used in any test class with the @Rule annotation as follows:
@Rule public TestRule myRule = new MyRule();Note: The member variable should be public. Let's take more examples. There is a good introduction in this tutorial. The author demonstrates how to implement two TestRules: one for SpringContext to use @Autowired in test classes and one for Mockito to populate the mocks before each test. An excellent example! I allow me to repeat the usage example.
public class FooTest { @Rule public TestRule contextRule = new SpringContextRule(new String[]{"testContext.xml"}, this); @Rule public TestRule mockRule = new MockRule(this); @Autowired public String bar; @Mock public List baz; @Test public void testBar() throws Exception { assertEquals("bar", bar); } @Test public void testBaz() throws Exception { when(baz.size()).thenReturn(2); assertEquals(2, baz.size()); } }This can not be achieved with two JUnit runners at once. E.g. you can not annotate a test class at the same time with @RunWith(Parameterized.class) and @RunWith(SpringJUnit4ClassRunner.class) or @RunWith(MockitoJUnitRunner.class).
But back to JSF. I want to show how to implement a TestRule for a simple and extensible JSF environment. First of all, we need a mock for FacesContext. We will implement it with Mockito - the most popular Java test framework. I have seen many different implementations, but in fact it is not difficult to implement a proper mock of FacesContext.
import javax.faces.context.FacesContext; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public abstract class FacesContextMocker extends FacesContext { private FacesContextMocker() { } private static final Release RELEASE = new Release(); private static class Release implements Answer<Void> { @Override public Void answer(InvocationOnMock invocation) throws Throwable { setCurrentInstance(null); return null; } } public static FacesContext mockFacesContext() { FacesContext context = Mockito.mock(FacesContext.class); setCurrentInstance(context); Mockito.doAnswer(RELEASE).when(context).release(); return context; } }For all PrimeFaces fan we will provide a similar mock for RequestContext.
import org.primefaces.context.RequestContext; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; public abstract class RequestContextMocker extends RequestContext { private RequestContextMocker() { } private static final Release RELEASE = new Release(); private static class Release implements Answer<Void> { @Override public Void answer(InvocationOnMock invocation) throws Throwable { setCurrentInstance(null); return null; } } public static RequestContext mockRequestContext() { RequestContext context = Mockito.mock(RequestContext.class); setCurrentInstance(context); Mockito.doAnswer(RELEASE).when(context).release(); return context; } }Now, a minimal JSF / Servlet environment could be set up as follows
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.faces.application.Application; import javax.faces.component.UIViewRoot; import javax.faces.context.ExternalContext; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.mockito.Mockito; import org.primefaces.context.RequestContext; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; public class JsfMock implements TestRule { public FacesContext mockFacesContext; public RequestContext mockRequestContext; public UIViewRoot mockViewRoot; public Application mockApplication; public ExternalContext mockExternalContext; public HttpSession mockHttpSession; public HttpServletRequest mockHttpServletRequest; public HttpServletResponse mockHttpServletResponse; @Override public Statement apply(final Statement base, final Description description) { return new Statement() { @Override public void evaluate() throws Throwable { init(); try { base.evaluate(); } finally { mockFacesContext.release(); mockRequestContext.release(); } } }; } protected void init() { mockFacesContext = FacesContextMocker.mockFacesContext(); mockRequestContext = RequestContextMocker.mockRequestContext(); mockApplication = Mockito.mock(Application.class); mockViewRoot = Mockito.mock(UIViewRoot.class); mockExternalContext = Mockito.mock(ExternalContext.class); mockHttpServletRequest = Mockito.mock(HttpServletRequest.class); mockHttpServletResponse = Mockito.mock(HttpServletResponse.class); mockHttpSession = Mockito.mock(HttpSession.class); Mockito.when(mockFacesContext.getApplication()).thenReturn(mockApplication); Mockito.when(mockApplication.getSupportedLocales()).thenReturn(createLocales().iterator()); Mockito.when(mockFacesContext.getViewRoot()).thenReturn(mockViewRoot); Mockito.when(mockViewRoot.getLocale()).thenReturn(new Locale("en")); Mockito.when(mockFacesContext.getExternalContext()).thenReturn(mockExternalContext); Mockito.when(mockExternalContext.getRequest()).thenReturn(mockHttpServletRequest); Mockito.when(mockHttpServletRequest.getSession()).thenReturn(mockHttpSession); Map<String, String> requestMap = new HashMap<String, String>(); Mockito.when(mockExternalContext.getRequestParameterMap()).thenReturn(requestMap); } private List<Locale> createLocales() { ArrayList<Locale> locales = new ArrayList<>(); locales.add(new Locale("en")); locales.add(new Locale("de")); ... return locales; } }We mocked the most used JSF / Servlet objects, linked them with each other and provided mocks via public member variables, so that they can be extended in test classes if needed. Below is an usage example which also demonstrates how to extend the mocked objects for a particular test.
public class PaymentRequestFormTest { private PaymentView paymentView; @Rule public JsfMock jsfMock = new JsfMock(); @Before public void initialize() { paymentView = mock(PaymentView.class); ... } @Test public void toJson() { // Mock URL and context path StringBuffer requestURI = new StringBuffer("http://localhost:8080/webshop"); Mockito.when(jsfMock.mockHttpServletRequest.getRequestURL()).thenReturn(requestURI); Mockito.when(jsfMock.mockHttpServletRequest.getContextPath()).thenReturn("/webshop"); // Invoke toJson method String json = PaymentRequestForm.toJson(jsfMock.mockFacesContext, paymentView); // Verify ... } }Any feedbacks are welcome.
Nice post! This blog has given me a better understanding. Thanks a lot for such an informative blog post. Cheers!
ReplyDeleteweb development company in jaipur
Thank you so much actually i am working on it now i resolved my problem great work!!
ReplyDeleteMr.S S Mishra
Your method really works great, I amended some scripts and everything works as it's supposed to, you can view here.
ReplyDelete"The concept behind TestRule is similar to custom JUnit runners, but without restrictions" I used a few runners for the project, but I have not noticed a significant difference in implementation.
ReplyDeleteproject from software development company
When I try to run a test case that makes use of the JsfMock TestRule, I get a "java.lang.ExceptionInInitializerError" which is caused by "java.util.MissingResourceException: Can't find javax.faces.LogStrings bundle". Looking at the stacktrace it appears that executing "mockViewRoot = Mockito.mock(UIViewRoot.class)" resulted in the exception being thrown. I've tried creating a Messages.properties file in the test classpath, but the exception is still being thrown. Is there something I'm missing in the setup?
ReplyDelete