My senior developer and I hit this weird problem yesterday trying to test a Spring controller with a mocked service. We are not using Mockito as this is a legacy system that we can't be bothered adding Mockito too.
The strangeness began when we attempted to use Reflection to change the @Autowired
service instance that was contained within the Controller after the application context was created.
We tried to use ReflectionUtils
, ReflectionTestUtils
and just vanilla reflection but the instance of the class would not change at all. We confirmed that the reflection was making the field accessable, because it is a private field (using field.isAccessible()
before and after the field.setAccessible(true)
, but the instance ID refused to change. This was confirmed multiple times by checking the instance using toString()
and also println()
statements in both the mocked and genuine services.
I'll post the code below:
Controller Test Class:
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);
}
}
Actual FooController implementation:
@Controller
@RequestMapping(value = "/foo")
public class FooController {
@Autowired
FooHelper fooHelper;
}
SpringTest class for reference:
@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();
}
}
default-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://ift.tt/1bHqwjR"
xmlns:xsi="http://ift.tt/ra1lAU"
xmlns:beans="http://ift.tt/GArMu6"
xmlns:tx="http://ift.tt/OGfeU2"
xsi:schemaLocation="http://ift.tt/1bHqwjR http://ift.tt/1bVJL9q
http://ift.tt/GArMu6 http://ift.tt/QEDs1e
http://ift.tt/OGfeU2 http://ift.tt/1dt4Cn6">
<!-- 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>
We eventually got it to work by changing a single line of code in the test class. We changed
fooController = applicationContext.getBean(FooController.class);
to
fooController = new FooController();
and the entire thing worked perfectly using both vanilla reflection and ReflectionTestUtils
.
We also tried adding a getter/setter inside the controller (yes, we were that desperate) and manually set the instance of FooHelper
inside the controller from the application context which also worked.
fooController = applicationContext.getBean(FooController.class);
fooController.setFooHelper(mockFooHelper);
response = new MockHttpServletResponse();
adapter.handle(request, response, fooController);
But our desire to get a test working was outweighed by our desire to NOT put a setter in a controller.
So our question is, why did Reflection fail to actually change the field instance. It was our understanding that Reflection is akin to the ultimate skeleton key for all Objects. We suspect that something in the Spring application context was either preventing the change from occurring or reverting the change at some point before the next line was run (because we had println()
on the very next line.
Aucun commentaire:
Enregistrer un commentaire