views:

63

answers:

2

I am testing a servlet's doPost() method using EasyMock objects for the HttpServletRequest and HttpServletResponse arguments. Within the doPost() method I'm testing the request and response objects are used as arguments to a static method class for another class, and I want to disregard (i.e. not record as expected) any calls made on the request and response objects within this method call (it's not relevant to this test anyway). For example the doPost() method of the servlet class I'm testing looks like this:

@Override
protected void doPost(final HttpServletRequest servletRequest,
                      final HttpServletResponse servletResponse)
    throws ServletException, IOException
{
    // handle an "updateFolder" event
    String eventParameter = servletRequest.getParameter("event");
    if ("updateFolder".equalsIgnoreCase(eventParameter))
    {
        // update the news documents folder settings
        String folderId = servletRequest.getParameter("folderId");
        IPortletContext portletContext = PortletContextFactory.createPortletContext(servletRequest, servletResponse);
        IPortletResponse portletResponse = portletContext.getResponse();
        portletResponse.setSettingValue(SettingType.CommunityPortlet, "NEWS_DOCUMENTS_FOLDER_ID", folderId);
    }

    // redirect to the appropriate URL
    servletResponse.sendRedirect(redirectUrl);
}

When the above code gets to the step where PortletContextFactory.createPortletContext() is called I don't really care what method calls are made on the request and response objects within that method, but if I pass in mock request and response objects when testing this method I get errors from EasyMock telling me that there are missing behavior definitions. For example I have a test method which looks like this:

@Test
public void testPostWithUpdate()
    throws Exception
{
    // create mock objects and record their expected calls
    HttpServletRequest mockServletRequest = createMock(HttpServletRequest.class);
    HttpServletResponse mockServletResponse = createMock(HttpServletResponse.class);
    IPortletResponse mockPortletResponse = createMock(IPortletResponse.class);
    IPortletContext mockPortletContext = createMock(IPortletContext.class);
    expect(mockServletRequest.getContextPath()).andReturn(null);
    expect(mockServletRequest.getParameter("event")).andReturn("updateFolder");
    expect(mockServletRequest.getParameter("folderId")).andReturn(null);
    expect(PortletContextFactory.createPortletContext(mockServletRequest, mockServletResponse)).andReturn(mockPortletContext);
    expect(mockPortletContext.getResponse()).andReturn(mockPortletResponse);
    mockPortletResponse.setSettingValue(SettingType.CommunityPortlet, "NEWS_DOCUMENTS_FOLDER_ID", null);
    mockServletResponse.sendRedirect(EasyMock.anyObject(String.class));

    // take the mock objects out of record state
    replay(mockPortletContext, mockPortletResponse, mockServletRequest, mockServletResponse);

    // instantiate an object of the class and run the method we want to test
    ControllerServlet controllerServlet = new ControllerServlet();
    controllerServlet.doPost(mockServletRequest, mockServletResponse);

    // verify that our mocks behaved as expected
    verify(mockPortletContext, mockPortletResponse, mockServletRequest, mockServletResponse);
}

I get the following error when I run the test class:

com.plumtree.openfoundation.util.XPIllegalStateException: missing behavior definition for the preceding method call getCharacterEncoding()
    at com.plumtree.openfoundation.util.XPException.GetInstance(XPException.java:397)
    at com.plumtree.openfoundation.util.XPException.GetInstance(XPException.java:350)
    at com.plumtree.openfoundation.web.XPRequest.InitRequest(XPRequest.java:201)
    at com.plumtree.openfoundation.web.XPRequest.<init>(XPRequest.java:111)
    at com.plumtree.remote.portlet.PortletContextFactory.createPortletContext(PortletContextFactory.java:32)
    at com.abc.servlet.ControllerServletTest.testPostWithUpdate(ControllerServletTest.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.IllegalStateException: missing behavior definition for the preceding method call getCharacterEncoding()
    at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:43)
    at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:73)
    at $Proxy4.setCharacterEncoding(Unknown Source)
    at com.plumtree.openfoundation.web.XPRequest.InitRequest(XPRequest.java:135)
    ... 25 more

I assume that the errors above are caused by not recording the method calls executed within the PortletContextFactory.createPortletContext() method on the mock request and response objects passed in as arguments. If this is actually what's happening here then how can I rework things so that the method calls made on the request and response mocks by the PortletContextFactory.createPortletContext() method are disregarded?

Thanks in advance for any suggestions.

--James

A: 

Try Mockito http://mockito.org/.

It is much easier to use from EasyMock, and doesn't force you to code all the method calls.

Eran Harel
Neither does EasyMock - it has _nice mocks_ for that. And ease of use is to a large extent a question of personal taste and experience.
Péter Török
@Peter I worked with both and now I prefer Mockito. This may be a matter of taste, still IMHO Mockito has a neater API. TO put it in other words, Mockito is a nice mock ;)
Eran Harel
+1  A: 

Perhaps you need something like this:

expect(mockServletRequest.getCharacterEncoding()).andReturn("UTF-8");

Or use createNiceMock() as Péter Török suggests.

Fly
You may encounter other methods not implemented by your mock, so createNiceMock() may work unless null causes other errors.
Fly
Yes, the above is what I'm trying to avoid, since I'm not sure what method calls are being made on the mock request within the third party class method, and it's not relevant anyway since I'm not testing that class. If the third party class implementation changes then my test may break using the approach described above. For example it looks like PortletContextFactory.createPortletContext() is calling getCharacterEncoding() on the mock request, and if the implementation of that method changes then my test will fail if I have that expectation on the mock object.
James Adams