Problem : I
have a Web Application which is deployed on tomcat server and I
needed to write unit test cases which can test all the controllers.
Secondly It internally also hits another Web server using
RestTemplate that means for the success of all unit test cases both
the applications should be up and running.
@RunWithSpring(SpringJunit4ClassRunner.class) runs the test with Spring custom runner that will load Spring application context from @ContextConfiguration(locations="file:WebContent/WEB-INF/spring-servlet.xml")
Note
: You must have observed that in
unit test case there are few imports which are static. To find the
Static imports in your Eclipse IDE you have to configure a bit. Goto
Java>Editor>Content Assist>Favorites and Add
https://github.com/spring-projects/spring-framework/tree/master/spring-test-mvc/src/test/java/org/springframework/test/web/client/match
Solution : After
spending some time on Google, I found that Spring provide a new
feature “MockRestServiceServer” by which testing a REST Client is
simple. This new feature is in Spring 3.2.x but also available in
Spring 3.1.x via the extra jar named “spring-test-mvc”. So, You
can test your Spring MVC controllers very easy without starting any
Web Application.
MockRestServiceServer
takes the approach of mocking the server and allowing you to specify
expected behavior and responses in your junit test class. It also
handles server exception classes.
MockRestRequestMatchers
offers many hamcrest matchers to check your request URL, headers,
HTTP method, and even json and xpath matchers to check body content.
MockRestResponseCreators
allows you to easily build both success and error responses.
This
blog will show you how the mock server works.
My
Configuration :
- Spring 3.1.x
- Junit 4
Step
1 : Add following dependency in your pom.xml
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.8.2</version><scope>test</scope></dependency><!-- This is not in Maven Central yet must include Spring snapshot repository --><dependency><groupId>org.springframework</groupId><artifactId>spring-test-mvc</artifactId><version>1.0.0.BUILD-SNAPSHOT</version><scope>test</scope></dependency><!-- For testing against latest Spring snapshots --><repositories><repository><id>org.springframework.maven.snapshot</id><name>Spring Maven Snapshot Repository</name><url>http://maven.springframework.org/snapshot</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository ></repositories>
Step
2: The next step is to define a Spring MVC controller that we will be
writing unit test for :
@Controller@RequestMapping("/test")public class RestController {private static final Logger LOG = LoggerFactory.getLogger(RestController.class);@AutowiredRestTemplate template;@AutowiredIRestService iRestService;@RequestMapping(value = "/google", method = RequestMethod.GET)@ResponseBodypublic String hitGoogle() {LOG.info("Notification : hit google command received from REST");return iRestService.hitGoogle();}}
Step
3: Write Impl class which actually perform REST Client operation to
get information from some another end Web application.
Step 4 : Finally the unit test class that uses Spring-test-mvc to mock the controller that returns the response as String.import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.client.HttpStatusCodeException;import org.springframework.web.client.RestTemplate;import com.waheed.mockserver.service.IRestService;@Servicepublic class RestServiceImpl implements IRestService {private static final Logger LOG = LoggerFactory.getLogger(RestServiceImpl.class);@AutowiredRestTemplate restTemplate;/*** REST client that makes a call to a URL and handle success and error by* returning them in error String.*/public String hitGoogle() {LOG.info("Testing google :");String response;try {response = restTemplate.getForObject("http://google.com",String.class);LOG.info("Response : {}", response);} catch (HttpStatusCodeException e) {LOG.error("Error while testing google", e);return "FAILED : " + e.getStatusCode();} catch (Exception e) {LOG.error("Error while testing google", e);return "FAILED : " + e.getStackTrace();}return response;}}
Here :import static org.junit.Assert.assertEquals;import static org.springframework.test.web.client.match.RequestMatchers.method;import static org.springframework.test.web.client.match.RequestMatchers.requestTo;import static org.springframework.test.web.client.response.ResponseCreators.withSuccess;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.HttpMethod;import org.springframework.http.MediaType;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import org.springframework.test.web.client.MockRestServiceServer;import org.springframework.web.client.RestTemplate;import com.waheed.mockserver.controller.RestController;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(locations="file:WebContent/WEB-INF/spring-servlet.xml")public class TestMockRestServiceServer {@Autowiredprivate RestTemplate restTemplate;private MockRestServiceServer mockServer;@AutowiredRestController restController;// Execute the Setup method before the test.@Beforepublic void setUp() {//create a mock Server instance for RestTemplatemockServer = MockRestServiceServer.createServer(restTemplate);}@Testpublic void testGoogleSuccess() {mockServer.expect(requestTo("http://google.com")).andExpect(method(HttpMethod.GET)).andRespond(withSuccess("SUCCESS",MediaType.TEXT_PLAIN));String response = restController.hitGoogle();mockServer.verify();assertEquals("SUCCESS",response);}}
@RunWithSpring(SpringJunit4ClassRunner.class) runs the test with Spring custom runner that will load Spring application context from @ContextConfiguration(locations="file:WebContent/WEB-INF/spring-servlet.xml")
@Before
annotation executes the setup method before the test. It will create
the mockServer for the restTemplate Instance.
@Test
executes the actual test
mockServer.expect(requestTo("URL")).andExpect(method(Method
Type))
.andRespond(withSuccess("RESPONSE
MESSAGE”,”MESSAGE FROMAT LIKE
JSON OR XML”));
If
the controller hit some URL which has exactly the same URL and method
type as we have set above then the spring-test-mvc will mock the
response as set above.
If using Spring 3.2.x:
org.springframework.test.web.client.match.MockRestRequestMatchers
org.springframework.test.web.client.response.MockRestResponseCreators
If using Spring 3.1.x, the static import classes are named differently:
org.springframework.test.web.client.match.RequestMatchers
org.springframework.test.web.client.response.ResponseCreators
Common
issue:
“java.lang.SecurityException: class "org.hamcrest.TypeSafeMatcher"'s signer information does not match signer information of other classes in the same package
at java.lang.ClassLoader.checkCerts(Unknown Source) “
There
are few ways to resolve the above issue :
Mockito
use Hamcrest
1.1 API
in the classpath and Eclipse JUnit 4 distribution also has the
Hamcrest Core 1.1 API.
- You may get this error when Hamcrest API is invoked from Eclipse JUnit’s and Not Mockito. The solution is change the ordering of the jar files in “Order & Export” tab of Eclipse build path.
Or simply removing the JUnit 4 library also fixes the problem apparently
(junit and hamcrest will be in the maven deficiencies anyway).
You can download the above tutorial form here.
References
:
http://docs.spring.io/spring/docs/3.2.x/javadoc-api/index.html?org/springframework/test/web/client/MockRestServiceServer.htmlhttps://github.com/spring-projects/spring-framework/tree/master/spring-test-mvc/src/test/java/org/springframework/test/web/client/match
Thanks man you made my day I was stuck on writing the test case on a deadline project and your example provided me exactly what I was supposed to do.
ReplyDelete