【问题标题】:Mockito / Spring MVC - (annotation-driven) request mapping dynamic verificationMockito / Spring MVC -(注解驱动)请求映射动态验证
【发布时间】:2012-02-29 22:23:30
【问题描述】:

我目前正在编写一个基于 Spring MVC 的 webapp。

我不想为每个带注释的方法编写一个测试,而是希望从参数化 JUnit 运行器中受益。

最后,我几乎可以正常工作了,尽管我必须在我的控制器方法中将所有原始参数更改为对应的包装器(然后手动对空引用进行完整性检查)。

如果有帮助,这里是代码(这也取决于 Guava):

@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 结束(原因:“参数不同!”)...

类型正确,但传递的实例与@Parameters 方法中设置的不同。

知道如何解决这个问题吗?

【问题讨论】:

    标签: java unit-testing spring-mvc junit4 mockito


    【解决方案1】:

    抓紧你的马!

    SpringSource 正在烘焙一个 spring-test-mvc 模块: https://github.com/SpringSource/spring-test-mvc

    【讨论】:

    • 正如我与 Piwai 讨论的那样(我认识他 IRL ^^),我最初的方法是错误的......测试给定的 HTTP 请求被路由到特定方法是一个实现问题......因此,功能测试我想要的最合适的方法是发送/模拟真实请求并检查返回的回复内容。简而言之,我会接受这个答案,因为这为我指明了正确的方向:)
    • 我没认出你-_-'嗨! :)
    【解决方案2】:

    最好不要提供有效的示例,而是提供无效的示例,并同时提供堆栈跟踪。

    我赶紧查了谷歌,好像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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-24
      • 1970-01-01
      • 1970-01-01
      • 2011-04-08
      • 1970-01-01
      • 2021-12-15
      相关资源
      最近更新 更多