2012-02-29 130 views
2

我目前正在編寫一個基於Spring MVC的web應用程序。Mockito/Spring MVC - (註解驅動)請求映射動態驗證

與其爲每個註釋的方法編寫一個測試,我想從參數化的JUnit跑步者中受益。我得到它幾乎工作,雖然我不得不在我的控制器方法(然後手動對空參考執行完整性檢查)所有原始參數更改爲其包裝對應

如果它可以幫助,這裏是代碼(這也取決於番石榴):

@RunWith(Parameterized.class) 
public class MyControllerMappingTest { 

    private MockHttpServletRequest request; 
    private MockHttpServletResponse response; 
    private MyController mockedController; 
    private AnnotationMethodHandlerAdapter annotationHandlerAdapter; 
    private final String httpMethod; 
    private final String uri; 
    private final String controllerMethod; 
    private final Class<?>[] parameterTypes; 
    private final Object[] parameterValues; 

    @Before 
    public void setup() { 
     request = new MockHttpServletRequest(); 
     response = new MockHttpServletResponse(); 
     mockedController = mock(MyController.class); 
     annotationHandlerAdapter = new AnnotationMethodHandlerAdapter(); 
    } 

    @Parameters 
    public static Collection<Object[]> requestMappings() { 
     return asList(new Object[][] { 
       {"GET", "/my/uri/0", "index", arguments(new MethodArgument(Integer.class, 0))} 
     }); 
    } 

    private static List<MethodArgument> arguments(MethodArgument... arguments) { 
     return asList(arguments); 
    } 

    public MyControllerMappingTest(String httpMethod, String uri, String controllerMethod, List<MethodArgument> additionalParameters) { 
     this.httpMethod = httpMethod; 
     this.uri = uri; 
     this.controllerMethod = controllerMethod; 
     this.parameterTypes = new Class<?>[additionalParameters.size()]; 
     initializeParameterTypes(additionalParameters); 
     this.parameterValues = newArrayList(transform(additionalParameters, valueExtractor())).toArray(); 
} 

    private void initializeParameterTypes(List<MethodArgument> additionalParameters) { 
     Iterable<Class<?>> classes = transform(additionalParameters, typeExtractor()); 
     int i = 0; 
     for (Class<?> parameterClass : classes) { 
      parameterTypes[i++] = parameterClass; 
     } 
    } 

    @Test 
    public void when_matching_mapping_constraints_then_controller_method_automatically_called() throws Exception { 
     request.setMethod(httpMethod); 
     request.setRequestURI(uri); 
     annotationHandlerAdapter.handle(request, response, mockedController); 

     Method method = MyController.class.getMethod(controllerMethod, parameterTypes); 
     method.invoke(verify(mockedController), parameterValues); 
    } 
} 

與下面的自定義類MethodArgument:

public class MethodArgument { 
    private final Class<?> type; 
    private final Object value; 

    public MethodArgument(final Class<?> type, final Object value) { 
     this.type = type; 
     this.value = value; 
    } 

    public Object getValue() { 
     return value; 
    } 

    public Class<?> getType() { 
     return type; 
    } 

    public static Function<MethodArgument, Class<?>> typeExtractor() { 
     return new Function<MethodArgument, Class<?>>() { 
      @Override 
      public Class<?> apply(MethodArgument argument) { 
       return argument.getType(); 
      } 
     }; 
    } 

    public static Function<MethodArgument, Object> valueExtractor() { 
     return new Function<MethodArgument, Object>() { 
      @Override 
      public Object apply(MethodArgument argument) { 
       return argument.getValue(); 
      } 
     }; 
    } 
} 

所以,我幾乎沒有,這裏唯一的測試用例是因爲Java Integer緩存而起作用的,因此Integer實例在整個調用鏈中都是相同的......但這不適用於自定義對象,我總是以InvocationTargetException(cause:「Argument (s)are different!「)...

這些類型是正確的,但傳入的實例與@Parameters方法中設置的實例不同。

任何想法如何解決這個問題?

回答

2

沉住氣!

SpringSource的被烘烤彈簧試驗-MVC模塊: https://github.com/SpringSource/spring-test-mvc

+0

當我同Piwai討論(我知道他IRL ^^),我最初的做法是錯誤的...測試一個給定的HTTP請求路由到一個特定的方法是一個實施的關注... 因此,功能測試我想要的最正確的方式是發送/模擬一個真正的請求,並檢查返回的答覆內容。簡而言之,我會接受這個答案,因爲這點我指向正確的方向:) – Rolf 2012-03-01 13:02:08

+0

而我不認識你-_-' 嗨! :) – Rolf 2012-05-18 13:06:48

1

如果不是提供可用的例子,而是提供不支持的例子,並提供堆棧跟蹤,那將會很好。

我很快查了Google,看來Mockito處理得不好reflection on spy objects

如果你真的想沿着這條路走,可能會有另一種方式:提供預期的被調用方法作爲參數化數據的一部分,而不是通過提供反射數據,而是通過從那裏實際調用模擬。

我正在寫,沒有在任何一方面IDE,所以有可能是編譯錯誤,但你會得到的想法:

@RunWith(Parameterized.class) 
public class MyControllerMappingTest { 

    public interface VerifyCall<T> { 
     void on(T controller); 
    } 

    @Parameters 
    public static Collection<Object[]> requestMappings() { 
     Object[][] testCases = {  
      {"GET", "/my/uri/0", new VerifyCall<MyController>() { 
       @Override 
       public void on(MyController controller) { 
        controller.index(0); 
       } 
      }} 
     }; 
     return asList(testCases); 
    } 

    private MockHttpServletRequest request; 
    private MockHttpServletResponse response; 
    private MyController mockedController; 
    private AnnotationMethodHandlerAdapter annotationHandlerAdapter; 

    private final String httpMethod; 
    private final String uri; 
    private final VerifyCall<MyController> verifyCall; 

    public MyControllerMappingTest(String httpMethod, String uri, VerifyCall<MyController> verifyCall) { 
     this.httpMethod = httpMethod; 
     this.uri = uri; 
     this.verifyCall = verifyCall; 
    } 

    @Before 
    public void setup() { 
     request = new MockHttpServletRequest(); 
     response = new MockHttpServletResponse(); 
     mockedController = mock(MyController.class); 
     annotationHandlerAdapter = new AnnotationMethodHandlerAdapter(); 
    } 

    @Test 
    public void when_matching_mapping_constraints_then_controller_method_automatically_called() throws Exception { 
     request.setMethod(httpMethod); 
     request.setRequestURI(uri); 
     annotationHandlerAdapter.handle(request, response, mockedController); 

     verifyCall.on(verify(mockedController)); 
    } 
} 

當然,有Java的Lambas將有助於使這個更具可讀性。

您還可以使用FunkyJFunctional

@RunWith(Parameterized.class) 
public class MyControllerMappingTest { 

    @Parameters 
    public static Collection<Object[]> requestMappings() { 
     class IndexZero extends FF<MyController, Void> {{ in.index(0); }} 
     Object[][] testCases = { //   
       {"GET", "/my/uri/0", withF(IndexZero.clas)} 

     }; 
     return asList(testCases); 
    } 

    private MockHttpServletRequest request; 
    private MockHttpServletResponse response; 
    private MyController mockedController; 
    private AnnotationMethodHandlerAdapter annotationHandlerAdapter; 

    private final String httpMethod; 
    private final String uri; 
    private final Function<MyController, Void> verifyCall; 

    public MyControllerMappingTest(String httpMethod, String uri, Function<MyController, Void> verifyCall) { 
     this.httpMethod = httpMethod; 
     this.uri = uri; 
     this.verifyCall = verifyCall; 
    } 

    @Before 
    public void setup() { 
     request = new MockHttpServletRequest(); 
     response = new MockHttpServletResponse(); 
     mockedController = mock(MyController.class); 
     annotationHandlerAdapter = new AnnotationMethodHandlerAdapter(); 
    } 

    @Test 
    public void when_matching_mapping_constraints_then_controller_method_automatically_called() throws Exception { 
     request.setMethod(httpMethod); 
     request.setRequestURI(uri); 
     annotationHandlerAdapter.handle(request, response, mockedController); 

     verifyCall.apply(verify(mockedController)); 
    } 
} 

一些旁註:

  • 對於可讀性的原因,這是把你靜態成員第一次在你的班上一個很好的做法。實例方法(setup())也應該在構造函數之後。

  • 數組語法:

代替此語法:

return asList(new Object[][] { 
    {}, 
    {} 
}; 

我覺得這句法更可讀:

Object[][] testCases = { 
    {}, 
    {} 
}; 
return asList(testCases);