我的高级开发人员和我昨天尝试使用模拟服务测试Spring控制器时遇到了这个奇怪的问题。我们没有使用Mockito,因为这是一个遗留系统,我们也不能为Mockito添加麻烦。
当我们在创建应用程序上下文后尝试使用Reflection来更改Controller中包含的@Autowired
服务实例时,就开始出现这种陌生感。
我们尝试使用ReflectionUtils
,ReflectionTestUtils
而只是使用vanilla反射,但类的实例根本不会改变。我们确认反射使该字段可访问,因为它是一个私有字段(在field.isAccessible()
之前和之后使用field.setAccessible(true)
,但实例ID拒绝更改。通过检查确认多次在模拟和真实服务中使用toString()
和println()
语句的实例。
我将发布以下代码:
控制器测试类:
public class PromoteFooControllerTest extends SpringTest {
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private AnnotationMethodHandlerAdapter adapter;
private FooController fooController;
@Autowired
@Qualifier("MockFooHelperSuccess")
private FooHelper mockFooHelper;
@Before
public void setUp() throws Exception {
adapter = new AnnotationMethodHandlerAdapter();
HttpMessageConverter[] messageConverters = {new MappingJacksonHttpMessageConverter()};
adapter.setMessageConverters(messageConverters);
/*Snipping boring request setup*/
}
@Test
public void promoteFooTest() {
System.out.println("Testing auto promote controller ");
try {
PromoteServiceResponse response = executePromoteController();
assertTrue(response.getBrokerCenterURL() != null);
assertTrue(response.getError() == null);
} catch (Exception e) {
e.printStackTrace();
fail("Should have passed.");
}
}
private PromoteServiceResponse executePromoteController() throws Exception {
fooController = applicationContext.getBean(FooController.class);
//Reflection happening here
Field field = fooController.class.getField("fooHelper");
field.setAccessible(true);
//Next line does nothing!!!
field.set(fooController, mockFooHelper);
response = new MockHttpServletResponse();
adapter.handle(request, response, fooController);
String fooServiceResponse = response.getContentAsString();
System.out.println(fooServiceResponse);
return Miscellaneous.fromJSON(new TypeReference<PromoteServiceResponse>() {
}, fooServiceResponse);
}
}
实际的FooController实现:
@Controller
@RequestMapping(value = "/foo")
public class FooController {
@Autowired
FooHelper fooHelper;
}
SpringTest类供参考:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/default-servlet.xml")
public class SpringTest implements ApplicationContextAware {
protected ApplicationContext applicationContext;
protected MockHttpServletRequest request;
protected ServletRequestAttributes attributes;
public void setApplicationContext(ApplicationContext applicationContext1)
throws BeansException {
applicationContext = applicationContext1;
}
protected void scopedBeansConfig() {
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) applicationContext
.getAutowireCapableBeanFactory();
configurableBeanFactory.registerScope("session", new SessionScope());
configurableBeanFactory.registerScope("request", new RequestScope());
request = new MockHttpServletRequest();
attributes = new ServletRequestAttributes(request);
RequestContextHolder.setRequestAttributes(attributes);
}
}
Mocked Helper:
@Component("MockFooHelperSuccess")
public class MockFooHelperSuccessImpl implements FooHelper {
@Override
public FooServiceResponse exceuteCreateFoo(String userName,
String password, String callingSystem, String keepLocking,
String updateToken, SourceFoo sourceFoo) {
return null;
}
@Override
public FooSearchResponse executeSearchFoo(String userName,
String password, FooSearchInput searchInput) {
return null;
}
@Override
public FooServiceResponse executeRetrieveFoo(String userName,
String password, String fooId, String integrationId,
String system, Boolean lockBar) {
return null;
}
@Override
public PromoteServiceResponse executePromoteFoo(String userName,
String password, String fooId, Boolean systemReminder) {
return PromoteServiceResponse.createSuccessResponse("testurl", "1-test");
}
@Override
public PromoteValidateResponse executeValidateFoo(String employeeId,
String personId, String userName, String password) {
return PromoteValidateResponse.createSuccessResponse();
}
}
默认-servlet.xml中:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Imports user-defined @Controller beans that process client requests -->
<beans:import resource="beans-config.xml" />
<beans:import resource="dozer-mapping-config.xml" />
<beans:import resource="aop-config.xml" />
</beans:beans>
AOP-config.xml中:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd">
<bean id="exceptionHandler" class="au.com.foosystem.exceptions.ExceptionHandlerImpl"/>
<aop:config>
<aop:aspect ref="exceptionHandler">
<aop:after-returning pointcut="execution(* au.com.foosystem.controller..*(..))" returning="result" method="sendExceptionEmail"/>
</aop:aspect>
</aop:config>
</beans>
我们最终通过更改测试类中的单行代码来实现它。我们改变了
fooController = applicationContext.getBean(FooController.class);
到
fooController = new FooController();
使用香草反射和ReflectionTestUtils
整个过程完美无缺。
我们还尝试在控制器中添加一个getter / setter(是的,我们是绝望的)并从应用程序上下文中手动设置控制器内FooHelper
的实例
fooController = applicationContext.getBean(FooController.class);
fooController.setFooHelper(mockFooHelper);
response = new MockHttpServletResponse();
adapter.handle(request, response, fooController);
但是,我们希望不将一个二传手放在一个控制器中,我们希望得到一个测试工作。
所以我们的问题是,为什么Reflection无法真正改变字段实例。我们的理解是,反射类似于所有物体的最终骨架键。我们怀疑Spring应用程序上下文中的某些内容要么阻止更改发生,要么在下一行运行之前的某个时刻恢复更改(因为我们在下一行上有println()
。
答案 0 :(得分:1)
大概你的控制器在au.com.foosystem.controller
包中,这是通过你的AOP配置建议的
<aop:config>
<aop:aspect ref="exceptionHandler">
<aop:after-returning pointcut="execution(* au.com.foosystem.controller..*(..))" returning="result" method="sendExceptionEmail"/>
</aop:aspect>
</aop:config>
要应用此建议,Spring需要代理您的控制器bean。也就是说,它将生成一个动态类的代理对象,该类扩展了您的FooController
类。此行为可能因版本而异,但在这种情况下,似乎代理对象实际上是指实际实例。
通过
fooController = applicationContext.getBean(FooController.class);
您将获得对代理对象的引用,该代理对象在内部保留对目标实际FooController
实例的引用。现在因为它是FooController
的动态子类,它通过继承也有一个FooHelper
字段,但它没有使用它,而是将所有调用委托给目标FooController
维护FooHelper
字段。