Unit testing Spring MVC controllers for web and RESTful services with spring-test-mvc
As Web application and RESTful web services are very common in enterprise applications, it is imperative to unit test the controllers. There are a number of strategies to unit test your controllers in an MVC framework. For example, running an embedded web server like jetty, and then run the unit tests against the web server, etc. The spring-test-mvc project makes testing your Spring MVC controllers very easy without starting an embedded server. This blog post will take you through the key steps involved.
Step 1: You need to have the right third-party libraries. The key ones to take note are spring-test-mvc, spring-test, json-path, hamcrest-library, hamcrest-core, and mockito-core.
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-library</artifactId> <version>1.3</version> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.8</version> <scope>runtime</scope> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.6</version> <scope>runtime</scope> </dependency> <!-- JSON XPATH library --> <dependency> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> <version>0.5.6</version> </dependency> <!-- spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.context</artifactId> <version>3.1.0.RELEASE</version> <exclusions> <!-- Exclude Commons Logging in favour of SLF4j --> <exclusion> <groupId>org.apache.commons</groupId> <artifactId>com.springsource.org.apache.commons.logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>3.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>3.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.1.0.RELEASE</version> <exclusions> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> </exclusions> </dependency> <!-- for tspring testing --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.1.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test-mvc</artifactId> <version>1.0.0.M1</version> </dependency>Step 2: The next step is to define a Spring MVC controller that we will be writing unit test for.
package com.myapp.accounting.aes.securities.pricehistory.controller; import java.sql.SQLException; import java.util.Date; import java.util.List; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.jdbc.CannotGetJdbcConnectionException; import org.springframework.security.authentication.LockedException; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import com.myapp.accounting.securities.pricehistory.model.PriceHistory; import com.myapp.accounting.securities.pricehistory.service.PriceHistoryService; import com.myapp.accounting.dm.model.refdata.securities.MarketDataSecurityPriceHist; @Controller public class PriceHistoryController { /** * Service for accessing the Market Data repository. Contains the business logic. */ @Resource(name = "aes_priceHistoryService") private PriceHistoryService securityPriceService; @RequestMapping(value = "/pricehistory", method = RequestMethod.GET) @ResponseBody public List<MarketDataSecurityPriceHist> getPriceHistory(@RequestParam(value="latestUpdatedTimeStamp", required=true) @DateTimeFormat(pattern="dd MMM yyyy HH:mm:ss") Date latestUpdatedTimeStamp, @RequestParam(value="maxRecords", required=false, defaultValue="100") int maxRecords) throws Exception { // call the business service PriceHistory result = securityPriceService.getPriceHistory(maxRecords, latestUpdatedTimeStamp); return result.getHistory(); } }
Step 3: The PriceHistory object that holds a list of "MarketDataSecurityPriceHist" objects.
package com.myapp.accounting.aes.securities.pricehistory.model; import com.myapp.accounting.dm.model.refdata.securities.MarketDataSecurityPriceHist; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; @XmlRootElement public class PriceHistory implements Serializable { private static final long serialVersionUID = 1L; private List<marketdatasecuritypricehist> history = new ArrayList<marketdatasecuritypricehist>(); @XmlElementWrapper(name = "historicalprices") @XmlElement(name = "price") public List<marketdatasecuritypricehist> getHistory() { return history; } public void setHistory(Listhistory) { this.history = history; } }
Step 4: Define the MarketDataSecurityPriceHist class that holds the relevant attributes.
package com.myapp.accounting.dm.model.refdata.securities; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; import javax.persistence.Entity; import javax.xml.bind.annotation.XmlElement; @Entity //persistence entity public class MarketDataSecurityPriceHist implements Serializable { private static final long serialVersionUID = 1444498748039607997L; private String securityCd; private Date priceDttm; private BigDecimal bidPrice; private BigDecimal closePrice; private BigDecimal lastPrice; private BigDecimal askPrice; private String lastUpdatedAction; private String LastUpdatedDetailUser; private Date lastUpdatedTimeStamp; private Date loadTimeStamp; private String pricecondition; private String recordType; private String priceTime; private String source; public MarketDataSecurityPriceHist() {} @XmlElement public String getSecurityCd() { return securityCd; } public void setSecurityCd(String securityCd) { this.securityCd = securityCd; } @XmlElement public Date getPriceDttm() { return priceDttm; } public void setPriceDttm(Date priceDttm) { this.priceDttm = priceDttm; } @XmlElement public BigDecimal getBidPrice() { return bidPrice; } public void setBidPrice(BigDecimal bidPrice) { this.bidPrice = bidPrice; } //other setters and getters are omitted }Step 5: Finally, and most importantly the unit test class that uses mockito to mock the PriceHistoryService implementation and spring-test-mvc to mock the controller that returns a json response.
package com.myapp.accounting.aes.securities.pricehistory; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.web.server.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.server.setup.MockMvcBuilders.standaloneSetup; import static org.springframework.test.web.server.result.MockMvcResultMatchers.*; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.http.MediaType; import com.myapp.accounting.aes.securities.pricehistory.controller.PriceHistoryController; import com.myapp.accounting.aes.securities.pricehistory.model.PriceHistory; import com.myapp.accounting.aes.securities.pricehistory.service.PriceHistoryService; import com.myapp.accounting.aes.securities.pricehistory.service.impl.PriceHistoryServiceImpl; import com.myapp.accounting.dm.model.refdata.securities.MarketDataSecurityPriceHist; public class PriceHistoryController2Test { private PriceHistoryService mockSecurityPriceService; private PriceHistoryController controller; @Before public void setup() { controller = new PriceHistoryController(); mockSecurityPriceService = mock(PriceHistoryServiceImpl.class); controller.setSecurityPriceService(mockSecurityPriceService); } @Test public void getPriceHistory() throws Exception { when( mockSecurityPriceService.getPriceHistory( (Integer) Mockito.any(), (Date) Mockito.any())) .thenReturn(getPriceHistoryTestData()); standaloneSetup(controller) .build() .perform( get( "/pricehistory?latestUpdatedTimeStamp=26 Jul 211 12:00:00&maxRecords=3000") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().type("application/json")) .andExpect(jsonPath("$.[0].securityCd").value("test")) .andExpect(jsonPath("$.[0].bidPrice").value(12.50)) .andExpect(jsonPath("$.[0].closePrice").value(12.50)); } //mock data that gets returned when getPriceHistory(...) method is called private static PriceHistory getPriceHistoryTestData() { PriceHistory ph = new PriceHistory(); List<marketdatasecuritypricehist> history = new ArrayList<marketdatasecuritypricehist>(); MarketDataSecurityPriceHist md = new MarketDataSecurityPriceHist(); md.setSecurityCd("test"); BigDecimal price = new BigDecimal("12.50"); md.setAskPrice(price); md.setBidPrice(price); md.setClosePrice(price); md.setLastUpdatedAction("SOME_ACTION"); md.setRecordType("RECORD_TYPE"); md.setLastUpdatedDetailUser("tesr"); Calendar cal = Calendar.getInstance(); cal.set(2010, 01, 01, 00, 00, 00); Date date = cal.getTime(); md.setLastUpdatedTimeStamp(date); md.setPriceDttm(date); System.out.println(date); history.add(md); ph.setHistory(history); return ph; } }
That's all to it for testing an MVC controller. The URL for above JSON RESTful web service will be something like
http://localhost:8080/accounting-server/securities/pricehistory?latestUpdatedTimeStamp=26+Jul+2010+12:00:00&maxRecords=3000
and the JSON data returned will be something like
[{"securityCd":"XX123","priceDttm":"2012-01-28","bidPrice":125.50,"closePrice":126.60,"lastPrice":124.50,"askPrice":123.80,"lastUpdatedAction":"NO ACTION","lastUpdatedTimeStamp":1327705288015,"loadTimeStamp":88150,"pricecondition":"NO_CONDITION","recordType":null,"priceTime":"10:01:28.150 AM","source":"HiPort","lastUpdatedDetailUser":"arul"}, {"securityCd":"YY321","priceDttm":"2012-01-28","bidPrice":125.50,"closePrice":126.60,"lastPrice":124.50,"askPrice":123.80,"lastUpdatedAction":"NO ACTION","lastUpdatedTimeStamp":1327705288015,"loadTimeStamp":88150,"pricecondition":"NO_CONDITION","recordType":null,"priceTime":"10:01:28.150 AM","source":"HiPort","lastUpdatedDetailUser":"arul"}]
Labels: Spring MVC, unit testing, Web services
1 Comments:
This comment has been removed by a blog administrator.
Post a Comment
Subscribe to Post Comments [Atom]
<< Home