2010-09-17 75 views
4

我正在測試使用EasyMock對象的HttpServletRequestHttpServletResponse參數的servlet的doPost()方法。在doPost()方法中,我正在測試請求,並且響應對象用作另一個類的靜態方法類的參數,並且我想忽略(即不按預期記錄)在此方法內對請求和響應對象所做的任何調用呼叫(無論如何,這與測試無關)。例如servlet類的doPost()方法我測試看起來像這樣:如何關閉EasyMock對象的錄製?

@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); 
} 

當上面的代碼獲取步驟,其中PortletContextFactory.createPortletContext()叫我真的不關心什麼方法調用上的要求而作出以及該方法中的響應對象,但是如果在測試此方法時傳遞模擬請求和響應對象,則EasyMock會收到錯誤,告訴我缺少行爲定義。比如我有一個測試方法,看起來像這樣:

@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); 
} 

我收到以下錯誤,當我運行測試類:

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 

我假設的錯誤以上均爲不記錄引起的方法調用在PortletContextFactory.createPortletContext()方法中對模擬請求和響應對象作爲參數傳入。如果實際上這裏發生了什麼,那麼我該如何重新修改事件,以便對請求進行的方法調用和PortletContextFactory.createPortletContext()方法的響應模擬都被忽略了?

回答

1

嘗試Mockito http://mockito.org/

從EasyMock使用起來非常容易,並且不會強制您編寫所有方法調用。

+1

EasyMock也沒有,它有_nice mocks_。易用性在很大程度上是個人品味和體驗的問題。 – 2010-09-17 21:55:01

+0

@彼得我與他們合作過,現在我更喜歡Mockito。這可能是一個味道的問題,仍然恕我直言Mockito有一個整潔的API。換句話說,Mockito是一個很好的模擬;) – 2010-09-18 12:27:58

1

也許你需要的東西是這樣的:

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

或者使用createNiceMock()彼得Török建議。

+0

你可能會遇到其他方法沒有被你的模擬實現,所以createNiceMock()可能工作,除非null導致其他錯誤。 – jbindel 2010-09-17 21:35:27

+0

是的,以上是我想要避免的,因爲我不確定在第三方類方法中對模擬請求進行什麼方法調用,並且它無關,因爲我沒有測試該類。如果第三方類的實現發生變化,那麼我的測試可能會使用上述方法中斷。例如,它看起來像PortletContextFactory.createPortletContext()在模擬請求上調用getCharacterEncoding(),如果該方法的實現更改,那麼如果我對模擬對象有這種期望,那麼測試將失敗。 – 2010-09-17 22:36:48

1

您需要模擬PortletContextFactory.createPortletContext致電。 EasyMock本身不支持靜態方法模擬,但是EasyMock的PowerMock擴展可以實現。下面是示例代碼,您應該插入您的測試:

mockStatic(PortletContextFactory.class);  
expect(PortletContextFactory.createPortletContext(mockServletRequest, mockServletResponse)).andReturn(mockPortletContext); 
replay(PortletContextFactory.class); 

還有2個要求:

  1. 使用 @RunWith(PowerMockRunner.class) 標註在 測試用例類級別。
  2. 在測試用例的類級別使用@PrepareForTest(PortletContextFactory.class)註釋。

更多詳情:http://code.google.com/p/powermock/wiki/MockStatic

+0

是的,PowerMock讓你做到這一點,但是這種方法只能用於遺留代碼,並且不得不在設計時使用Powermock來測試類,這肯定是設計不好的一個標誌。 – 2012-04-18 12:19:44

+0

@ Pierre-Henri是的,應該儘量避免使用靜態方法和模擬。然而_OP_在他的問題中沒有明確提到他是在測試遺留代碼還是在設計一個新代碼(或者至少有權修改它)。 – Idolon 2012-04-19 13:07:20

1

替代做法在類似的測試:

class Class_Under_Test { 
    public void A() { 
    B b = C.create(); //create is static 
    D d = e.(b); 
    } 
} 

爲了解決在EasyMock的靜態參考問題,你可以在你的方法定義修改爲:

@VisibleForTesting 
B create() { 
    return C.create(); 
} 

public A() { 
    B b = create(); 
    D d = e.(b); 
} 

在您的測試課程中,您可以這樣做:

Class testSomething { 

    private Mock_Class_Under_Test mockOject;//Class Defined below 
    private B mockB; 

    @Override 
    void setup() { 
    mockB =createMock(B.class); 
    } 

    private class Mock_Class_Under_Test extends Class_Under_Test { 

     @Override 
     B create() { 
     return mockB; 
     } 
    } 

    public void testA() { 
    //No need to put expectation on static call as create() will always return mock. 
    expect(e.something(mockB)).andReturn(somethingElse); 
    } 
} 

所有進一步的測試將使用mockObject來調用Class_Under_Test的方法。

1

要開始錄製,請使用一個很好的模擬。從文檔:

createMock()的默認行爲所有 方法返回的Mock對象是拋出一個AssertionError所有意想不到的方法調用。 如果您希望「默認」模擬對象默認允許所有 方法調用並返回相應的空值(0,null或false),則使用createNiceMock()代替。

另外,更一般地,你想要模擬的是createPortletContext(servletRequest, servletResponse)。不幸的是,這是一個靜態調用。 爲了實現這個模擬,創建你自己的工廠,它將返回portletContext,並將這個工廠傳遞給你測試的類(最好在構造函數中)。 嘲笑這個工廠和portletContext,所以你只能測試這裏的重要事情:你寫的代碼。

+1

一個單獨的'portletContext'工廠是一個好方法。然而,@OP將不能將它傳遞給構造函數,因爲他正在測試一個servlet(它需要公共無參數構造函數)。但是可以在servlet中創建一個setter,並在測試用例中調用它來傳遞工廠的模擬版本(顯然,默認的工廠實現必須在servlet的默認構造函數中設置) – Idolon 2012-04-19 13:40:02

+0

有關默認構造函數的好處:) – 2012-04-19 18:41:12

+0

對於這個問題,最好的辦法是在我看來,將doPost調用委託給可以完全控制的計劃java服務。 這可能是矯枉過正,所以使用setter的@Idolon方法也是完全有效的。 – 2012-04-19 18:51:30