我为一些Spring MVC控制器编写了JUnit测试。 JUnit测试的初始化对于我的所有Controllers测试都是通用的,所以我想创建一个执行此初始化的抽象类。
因此,我创建了以下代码:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:spring/applicationContext-test.xml", "classpath*:spring/spring-mvc-test.xml" })
@Transactional
public abstract class AbstractSpringMVCControllerTest<T> {
@Autowired
protected ApplicationContext applicationContext;
protected MockHttpServletRequest request;
protected MockHttpServletResponse response;
protected HandlerAdapter handlerAdapter;
protected T controller;
@SuppressWarnings("unchecked")
@Before
public void initContext() throws SecurityException, NoSuchFieldException {
request = new MockHttpServletRequest();
response = new MockHttpServletResponse();
handlerAdapter = applicationContext.getBean(AnnotationMethodHandlerAdapter.class);
// Does not work, the problem is here...
controller = applicationContext.getBean(T);
}
}
我的想法是为每个控制器创建一个我想要测试扩展我的AbstractSpringMVCControllerTest
的JUnit测试类。 extends
声明中提供的类型是控制器的类。
例如,如果我想测试我的AccountController
,我会像这样创建AccountControllerTest
类:
public class AccountControllerTest extends AbstractSpringMVCControllerTest<AccountController> {
@Test
public void list_accounts() throws Exception {
request.setRequestURI("/account/list.html");
ModelAndView mav = handlerAdapter.handle(request, response, controller);
...
}
}
我的问题位于抽象类的initContext()
方法的最后一行。这个抽象类将controller
对象声明为T
对象,但是怎么能说Spring Application Context返回类型T
的bean?
我尝试过这样的事情:
Class<?> controllerClass = this.getClass().getSuperclass().getDeclaredField("controller").getType();
controller = (T) applicationContext.getBean(controllerClass);
但是controllerClass
会返回java.lang.Object.class
课程,而不是AccountController.class
。
当然,我可以创建一个public abstract Class<?> getControllerClass();
方法,它将被每个JUnit Controller测试类覆盖,但我更愿意避免这种解决方案。
那么,任何想法?
答案 0 :(得分:4)
如果你的AbstractSpringMVCControllerTest
的子类在编译时绑定T
,那么这是可能的。也就是说,你有像
public class DerpControllerTest extends AbstractSpringMVCControllerTest<DerpController> { }
而不是
public class AnyControllerTest<T> extends AbstractSpringMVCControllerTest<T> { }
我猜你可能有前者。在这种情况下,T
的类型在运行时从Class
对象AbstractSpringMVCControllerTest
中删除,但Class
的DerpControllerTest
对象执行< / em>提供了一种了解T
是什么的方法,因为它在编译时绑定了T
。
以下课程演示了如何访问T
的类型:
<强> Super.java 强>
import java.lang.reflect.ParameterizedType;
public abstract class Super<T> {
protected T object;
@SuppressWarnings("unchecked")
public Class<T> getObjectType() {
// This only works if the subclass directly subclasses this class
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
}
<强> Sub.java 强>
public class Sub extends Super<Double> {
}
<强> Test.java 强>
public class Test {
public static void main(String...args) {
Sub s = new Sub();
System.out.println(s.getObjectType()); // prints "Class java.lang.Double"
}
}
答案 1 :(得分:3)
这与我们通常看到的类型擦除不同。对于类型擦除,我们不知道当前类的参数(使用getClass()
获得的参数),但是您可以获得超类/超级接口(使用getGenericSuperxxxxx()
获得的那些参数)因为这是类型声明的一部分。
这不会提供controller
字段的类型,但我希望这足以达到您的目的。
代码:
public class A<P> {
}
import java.lang.reflect.ParameterizedType;
public class B extends A<String> {
public static void main(String[] arg) {
System.out.println(
((ParameterizedType)B.class.getGenericSuperclass()).getActualTypeArguments()[0]);
}
}
输出:
class java.lang.String
在你的情况下,它将是
Class controllerClass = (Class)( ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
需要注意的事项:
如果类B
也参数化如下:
public class B<X> extends A<X> {}
这不起作用。或者如果你有另一个类扩展B,它也会有问题。我不会讨论所有这些情况,但你应该明白这一点。
答案 2 :(得分:1)
你不能因为在运行时由于ERASURE,JVM无法知道你的“controller”属性的类。它被视为对象......