I am trying to unit test a Spring MVC based Controller.
This controller calls out to a service. That service is actually remote, and is exposed to the controller via JSON-RPC, and specifically, a com.googlecode.jsonrpc4j.spring.JsonProxyFactoryBean
The Controller has:
#Controller
#RequestMapping("/users")
public class UserController {
/**
* ID manager service that will be used.
*/
#Autowired
IdMService idMService;
...
#ResponseBody
#RequestMapping(value = "/{userId}", method = GET)
public UserAccountDTO getUser(#PathVariable Long userId, HttpServletResponse response) throws Exception {
try {
return idMService.getUser(userId);
} catch (JsonRpcClientException se) {
sendJsonEncodedErrorRepsonse(response, se);
return null;
}
}
...
}
The spring configuration provides the IdMService like this:
<!-- Create the proxy for the Access Control service -->
<bean class="com.googlecode.jsonrpc4j.spring.JsonProxyFactoryBean">
<property name="serviceUrl" value="${access_control.service.url}" />
<property name="serviceInterface" value="com.phtcorp.service.accesscontrol.IdMService" />
</bean>
Thus, the IdMService that gets injected into the controller is actually a JSON-RPC proxy, implementing the IdMService interface.
I would like to test the controller, but mock the IdMService. I have this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/test-context.xml" })
#SuppressWarnings("javadoc")
public class TestUserController {
#Autowired
private ApplicationContext applicationContext;
private HandlerAdapter handlerAdapter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
#Mocked IdMService service;
#Test
public void getUser() throws Exception {
request.setMethod(RequestMethod.GET.name());
request.setRequestURI("/users/1");
HandlerMethod handler = (HandlerMethod) getHandler(request);
handlerAdapter.handle(request, response, handler);
new Verifications() {{
service.getUser(1L); times=1;
}};
}
...
}
However, I find that IdMService that is injected into the controller is not a mock, it is a JsonRpcProxy after all. I have successfully tested a different controller in this manner, but that one does not use a proxy to its service.
So the question is: how do I use jmockit to cause a mock IdMService to be injected into the UserController? Note that I'm not instantiating the UserController myself, anywhere; spring/spring-mvc does that.
Thanks for any help!
If you are unit testing your UserController, why not just instantiate it yourself. Get Spring out of the picture and just test it all on its own.
You are not testing the UserController here so much as testing the Spring wiring of it and the request mapping for it.
Note that I'm not instantiating the UserController myself, anywhere; spring/spring-mvc does that.
This means that you're not writing a unit test. This is testing the spring wiring which makes it an integration test. When writing a unit test you instantiate the class under test and supply the dependencies yourself. This allows you to isolate the logic in the class being tested by providing mocked instances of the classes dependencies. That's where jmockit comes in.
I solved my problem by injecting the mock myself:
#Mocked IdMService service;
#Before
public void setUp() {
controller.setIdMService(service);
...
}
Related
I am trying to write an integration test using Spring Boot that tests the transaction logic in one of my controllers.
What the test should do, is the following:
Inject one of my controllers using #Inject
Replace an email dependency in the controllers dependencies with a Mock, to avoid actually sending an email during the integration test.
Call a method of the controller
Assert that the transactions of the called method are properly rolled back when the mail sending mock throws an exception.
Now my problem is that, when the test runs, the controller is injected into my test class but all its dependencies are null. Here is my integration test:
#RunWith(SpringJUnit4ClassRunner.class)
#IntegrationTest
#SpringApplicationConfiguration(App.class)
#WebIntegrationTest
public MyIntegrationTest () {
#Inject MyController controller;
#Before
public void before () {
// replace one particular dependency of controller with a mock
}
#Test
public void testFoo () { ... }
}
Due to the test being an integration test which starts up a full spring web application context, I was expecting that my controller would have all its dependencies already autowired, but that is obviously not the case and instead all dependencies are set to null.
Question: Do I need to use some additional annotations, or setup something in my #Before method? Or am I approaching the problem from a completely wrong side?
Update: Is it possible to test my Spring MVC Layer, without testing via HTTP such as with TestRestTemplate or MockMvc? But by directly
Test with TestRestTemplate instead of injecting the controller itself. Controllers is obviously a spring bean but if you directly inject it in your test class, it wont be able to initialize the context.
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExampleStart.class)
public class ExampleTest {
#Autowired
private TestRestTemplate restTemplate;
#Test
public void exampleTest() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
}
ExampleStart.java -> The spring boot starter class
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class ExampleStart extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExampleStart.class);
}
public static void main(String[] args) {
SpringApplication.run(ExampleStart.class, args);
}
}
Ref : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
But if you want to test service method, you can use #Autowired and call the methods as usual.
I have seen example , how to call spring controller using mockito.
Using Mock I call Spring MVC controller.
Controller Invokes Spring service class.
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/spring/root-context.xml" })
public class TestController {
#Mock
private TestService testService;
#InjectMocks
private PaymentTransactionController paymentController;
private MockMvc mockMvc;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
}
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
}
Ok it works well. I calls my Spring controller very well. But In Spring controller I have Injected Service Layer.
#Autowired
private TestService serviceTest;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
serviceTest.save();
// in save method I call dao and dao perist data;
// I have injected dao intrface in serviceTest layer
...
return result;
}
The problem is that, my app does not invokes save method, it is not entered in it. I have no error too. The same result is when I call save() method from Junit (I have commented it in test() method).
When I debug, I have seen that interrupt method happens of org.mockito.internal.creation.MethodInterceptorFilter
How to solve this problem? what happens?
If you are doing a unit test of your controller, you should mock the service layer (what you are doing). In this kind of test, you just control that :
the correct methods of the controller are triggered and they produce what is expected
the correct methods in service layer are called ... in the mock
You simply have to configure the return values of the methods of the mock (if relevant), or control what was called
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.setMockMvc(MockMvcBuilders.standaloneSetup(paymentController).build());
// set return values from the mocked service
when(testService.find(1)).thenReturn(...);
}
and verify later what has been called
#Test
public void test() throws Exception {
this.mockMvc.perform(post("/tr/test").content(...)).andExpect(status().isOk());
// testService.save(); <-- another way
verify(testService, times(1)).save();
}
If you want to do an integration test, you do not mock the service, but setup an application context to inject real beans, but ordinarily use an embedded database instead of the real one.
just change #InjectMocks to #Autowired. This fix the issue! In this case you are not mocking, you are invoking method with real data.
As I understand, you perform post to "/tr/test" resource, but request mapping in your controller is '/payment'. Make sure you post to resource mapped in the controller.
I would like to be able to test a route which consumes from a queue then does some work in a bean involving a spring injected service and use mockito to effectively mock out this service.
My spring route is as follows:
<camel:route id="msgemailqueue-to-emailservice">
<camel:from uri="activemq:emails" />
<camel:bean ref="emailService" method="createEmailRequest"/>
</camel:route>
The emailService bean has an autowired service which is then called in the createEmailRequest() which goes off to another service and retrieves user data to be used subsequently.
The test:
#RunWith(MockitoJUnitRunner.class)
public class TroubledEmailServiceImplTest extends CamelSpringTestSupport {
#Produce(context = "messagingCamelContext")
protected ProducerTemplate producer;
#Mock
private UserRestService userRestService;
#Override
protected AbstractApplicationContext createApplicationContext() {
return new ClassPathXmlApplicationContext("messaging-camel-route-test-context.xml");
}
#Test
public void testUserResponseToEmailQueue() throws Exception {
context.addRoutes(new MyDynamcRouteBuilder(context, "direct:addEmailRequest", "activemq:emails"));
Mockito.when(userRestService.getUserById(Mockito.anyLong())).thenReturn(
new WebServiceResult<UserVO>(new UserVO()));
CreateMessageRequest msgReq = new CreateMessageRequest();
producer.sendBody("direct:addEmailRequest", msgReq);
Mockito.verify(userRestService).getUserById(Mockito.anyLong());
assertMockEndpointsSatisfied();
}
The bean as follows:
#Override
public void createEmailRequest(final CreateMessageRequest request) throws CreateEmailException {
LOGGER.trace("Entering createEmailRequest(request) " + Arrays.asList(new Object[] { request }));
Validate.notNull(request, "CreateMessageRequest was null");
WebServiceResult<UserVO> response;
try {
response = userRestService.getUserById(request.getId());
} catch (final WebServiceException e) {
throw new CreateEmailException("Error lookup up user data for email", e);
}
final UserVO userResponse = response.getData();
All compiles ok and when running the route fires as an object is popped on the queue which is then passed to the bean and the createEmailRequest is invoked and the call to the mockito mocked service happens ok
response = userRestService.getUserById(request.getId());
but the response is null even though
Mockito.when(userRestService.getUserById(Mockito.anyLong())).thenReturn(
new WebServiceResult<UserVO>(new UserVO()));
was performed in the test. It appears that the service in bean is a different instance i.e. mockito mock is never invoked.
I am doing something wrong and perhaps my testing approach is all wrong as well but should this work in theory? I'd really like to be able to mock out a service in a bean in my camel route.
I'm using Camel Enhanced Spring Test and have passed through the same issue. I only changed #Mock to #MockBean. My Camel version is 2.18.
the mock userRestService you create in the test has to be the same instance you use in the bean. I do not see where you are setting the userRestService for the createEmailRequest method. That service needs to be the same mock object as you create in your test.
I have resolved this - mea culpa. My test class was effectively creating two instances of the service - one through the spring application context and another due to the #RunWith(MockitoJUnitRunner.class) plus #mock annotation. Now resolved by doing the mock creation once. To sum up this was a spring wiring issue only on my part. Many thanks #mike-pone.
I have a bean being created by a service with the following class:
#Configuration
public class AccessManager {
#Bean(name="access", destroyMethod="destroy")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Autowired
public Access create(HttpServletRequest request) {
System.out.println(request.getRemoteAddr());
return new Access();
}
}
Everything works as expected, except that when the application is starting, this method is being called, probably because I have some other singleton beans that use the Access bean. At the start up there is no request bound to the Thread, and it's expected to get a java.lang.IllegalStateException when trying to access any property of the request parameter.
No problem. The question is, is it possible to check if the underlying HttpServletRequest of the proxy request is null before calling a property that raises the exception?
You probably want to take a look at RequestContextHolder#getRequestAttributes(). That will return null if you're not currently in a context where request scope could be used.
#Configuration
public class AccessManager {
#Bean(name="access", destroyMethod="destroy")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#Autowired
public Access create(HttpServletRequest request) {
if (RequestContextHolder.getRequestAttributes() != null) {
System.out.println(request.getRemoteAddr());
}
return new Access();
}
}
I think the issue here is with separation of concerns. Usually your service layer should not have any dependency on the servlet classes. This is very much a controller/UI concern.
Your service class should be provided with the properties which it needs to do its job. In this case a String. This service method should be called from a controller method which is injected with the servlet request.
Something like the following:
#Controller
public class MyController {
#Autowired
private AccessManager accessManager;
#RequestMapping
public void handleRequest(HttpServletRequest request) {
accessManager.create(request.getRemoteAddr());
}
}
and your service would then look like this:
#Service
public class AccessManager {
public Access create(String remoteAddress) {
return new Access();
}
}
To sum up, anything annotated as #Service shouldn't have access to the Request.
I have two related issues regarding spring/AspectJ AOP. I have a typical logger aspect which logs exceptions thrown from any class in my application including services, daos, controllers and webflow actions...
#Aspect
public class AspectLogger {
#AfterThrowing(pointcut = "execution(* com.myapp..*.*(..))", throwing = "t")
public void logGustavoException(JoinPoint joinPoint, Throwable t) {
Log logger = LogFactory.getLog(joinPoint.getTarget().getClass());
logger.error(t.getMessage(), t);
}
}
In my application context I have an equally typical configuration...
<context:annotation-config />
<!-- AOP logging config -->
<aop:aspectj-autoproxy>
<aop:include name="aspectLogger" />
</aop:aspectj-autoproxy>
<bean id="aspectLogger" class="com.myapp.AspectLogger" />
This works fine for the most part, the issue I have is with the webflow actions and controllers which implement an interface.
1 - Controllers which implement an Interface...
One of our controllers implements an interface which defines one method, as well as defining several public methods which are used as #RequestMapping handlers...
#Controller
public class AmazingController implements OutstandingInterface {
// implements the method from OutstandingInterface
#Override
public Object doSomethingOutstanding(){
...
}
#RequestMapping(value="/amazingUrl.htm", method = RequestMethod.GET)
public String doSomethingAmazing(HttpSession session, ModelMap model) {
return "anAmazingViewName";
}
...
}
The issue here is that due to the fact that the controller implements an interface that doesn't define all its public methods (i.e. controller request mapping methods), a proxy is created for the controller which only proxies the 'doSomethingOutstanding' method from OutstandingInterface. As such, when a request comes in to /amazingUrl.htm, Spring does not route it to the appropriate request handler - it's as though the request mapping doesn't exist. I have solved this by defining an interface for the controller which extends OutstandingInterface and also defines the request handler methods required by the controller, but it seems odd/wrong to me to have to define an interface for a controller just so that the AspectJ stuff doesn't 'hide' the request handler...
#Controller
public interface IAmazingController extends OutstandingInterface{
#RequestMapping(value="/amazingUrl.htm", method = RequestMethod.GET)
public String doSomethingAmazing(HttpSession session, ModelMap model);
}
...
public class AmazingController implements IAmazingController {
#Override
public Object doSomethingOutstanding(){
...
}
#Override
#RequestMapping(value="/amazingUrl.htm", method = RequestMethod.GET)
public String doSomethingAmazing(HttpSession session, ModelMap model) {
return "anAmazingViewName";
}
...
}
2 - Webflow Actions
The second issue is very similar. After introducing the AspectJ configuration, none of my webflow Action classes were being autowired correctly - I kept getting 'cannot find bean of type FantasticAction' sort of errors. Again, I introduced interfaces for all of the Action classes and this solved the problem as it was the proxy that was being injected at runtime, not the actual action implementation class.
So finally... the question in both instances is - is there a way of getting around these AspectJ issues without having to define interfaces for every class I want to advise?
You should add CGLIB dependendy in your class path so you will not need to create interfaces for working with AOP
Take a look to the doc.