Maven Integration Testing And Spring Restful Services
- We use Maven to build and unit test some Spring based restful webservices.
- We then use the Maven Jetty plugin to start a Web server and deploy them to it.
- We create an In-memory database and create the schema
- Finally we run all of the integration tests in the seperate \src\integrationtest\java directory
Code Structure
Running the Example
The full code is hosted at google code. Use the following commands to check it out and run it. Make sure you have nothing running on port 8080 before running the tests.
svn co https://designbycontract.googlecode.com/svn/trunk/examples/maven/spring-rest-example cd spring-rest-example mvn clean install -Pit,jetty
You can see the full build on the following Cloudbees hosted Jenkins instance. https://designbycontract.ci.cloudbees.com/job/spring-rest-example/
Results of running the example
- The tests in the standard maven test structure are run during the unit test phase as usual.
- A Jetty Webserver is started
- The war containing the web server is deployed to the server
- The hsqldb in-memory database is started and the schema created.
- The tests in the \src\integrationtest\java directory are run during the integration test phase.
- The server is shutdown.
How to create the Spring Service class
The trade service is very simple. It uses a repository to create and find trades. I haven’t included exceptions to keep the whole thing as simple as possible. The only trick here is to add the @Service annotation, otherwise it is straight Java.
@Service public class SimpleTradeService implements TradeService { @Autowired TradeRepository tradeRepository; public SimpleTradeService(TradeRepository tradeRepository) { this.tradeRepository = tradeRepository; } @Override public Long createTrade(Trade t) { Long id = tradeRepository.createTrade(t); return id; } @Override public Trade getTradeById(Long id) { return tradeRepository.getTradeById(id); }
How to create the Database repository class
The above service uses a trade repository to create and find trades. We use the Spring class HibernateDaoSupoort to create this class and keep things simple. By extending this class we simply need to create our trade object class, and define our database details in the spring config. All of the other details are taken care of by the framework.
public class HibernateTradeRepository extends HibernateDaoSupport implements TradeRepository{ @Override public Trade getTradeByReference(String reference) { throw new RuntimeException(); } @Override public Long createTrade(Trade trade) { return (Long) getHibernateTemplate().save(trade); } @Override public Trade getTradeById(Long id) { return getHibernateTemplate().get(Trade.class, id); } }
How to create the Database Trade Class
We use standard JPA annotations to define our database trade object
@Entity public class Trade { @Id private long id;
The @Entity annotation marks the object as a database entity. The @Id annotation shows which field we want to be our table primary key. For the rest of the fields we use default behaviour so no other annotations are required.
How to Configure the Database
For this example we are going to use Hsqldb to create our database. http://hsqldb.org/ A new instance of this will be created every time we start the server. To setup the database all we have to do is define it in the spring config trade-servlet.xml
<bean id="sessionFactory" <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="packagesToScan" value="com.dbc.model" /> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </prop> <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> <prop key="hibernate.connection.pool_size">0</prop> <prop key="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</prop> <prop key="hibernate.connection.url"> jdbc:hsqldb:target/data/tradedatabase;shutdown=true </prop> <prop key="hibernate.connection.username">sa</prop> <prop key="hibernate.connection.password"></prop> <prop key="hibernate.connection.autocommit">true</prop> <prop key="hibernate.jdbc.batch_size">0</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean>
The session factory defines our database connection details. The most important property is
<prop key="hibernate.hbm2ddl.auto">update</prop>
This property tells hibernate to update the database when the application starts. It will effectively create the table for the trade object from the annotations on our trade object. When you run the tests, you will see that the following SQL is executed on startup.
11:30:31,899 DEBUG org.hibernate.tool.hbm2ddl.SchemaUpdate SchemaUpdate:203 - create table Trade (id bigint not null, description varchar(255), reference varchar(255), primary key (id))
Thats a new database setup and ready to go.
Creating The Restful Interface.
I’m just going to cover the basics here. For some great examples follow these links http://blog.springsource.com/2009/03/08/rest-in-spring-3-mvc/ http://www.stupidjavatricks.com/?p=54
How to Create the Spring Controller
The Spring controller is the key to this whole example. It is the controller that takes our requests and passes them to the trade Service for processing. It defines the restful interface. We use @PathVariable to make things simple.
@RequestMapping(value = "/create/trade/{id}") public ModelAndView createTrade(@PathVariable Long id) { Trade trade = new Trade(id); service.createTrade(trade); ModelAndView mav = new ModelAndView("tradeView", BindingResult.MODEL_KEY_PREFIX + "trade", trade); return mav; } @RequestMapping(value = "/find/trade/{id}") public ModelAndView findTradeById(@PathVariable Long id) { Trade trade = service.getTradeById(id); ModelAndView mav = new ModelAndView("tradeView", BindingResult.MODEL_KEY_PREFIX + "trade", trade); return mav; }
It works quite simply by populating the @PathVariable id with the value from /find/trade/{id} For example, requesting /find/trade/1 will populate reference with “1” requesting /find/trade/29 will populate reference with “29” More information can be found here: http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates
How to configure the Web Application
The configuration of the web application in web.xml is very straightforward. First we register the Spring Servlet
trade org.springframework.web.servlet.DispatcherServlet 1
Next we define a mapping to the servlet. This mapping will pass all requests to our servlet.
trade /*
How to Configure Spring
The Spring configuration consists of a number of distinct elements. The first line simply tells Spring where to look for annotations
The BeanNameViewResolver takes the name
This scary looking piece of XML does the job of making sure that the Trade object is returned as XML. XStream will take the object and automatically convert to an XML Format.
The Trade class defines the XStream annotation for this.
@XStreamAlias("trade") public class Trade {
In our case you can see from the test that we get the following from /search/trade/1
1
How to start and stop the Jetty Server
I use the Jetty Plugin to start the server and deploy the war file contained the services. http://docs.codehaus.org/display/JETTY/Maven+Jetty+Plugin The server is started with the following snippet from pom.xml
<execution> <id>start-jetty</id> <phase>pre-integration-test</phase> <goals> <goal>run</goal> </goals> </execution>
The server is stoped with the following snippet from pom.xml
<execution> <id>stop-jetty</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution>
How to run the Integration Tests
The integration tests are run using failsafe as described in the orgiinal article. http://johndobie.blogspot.com/2011/06/seperating-maven-unit-integration-tests.html We use the new Spring RestTemplate to make the call to the service easy.
@Test public void testGetTradeFromRestService() throws Exception { long id = 10L; createTrade(id); String tradeXml = new RestTemplate() .getForObject( "http://localhost:8080/find/trade/{id}", String.class, id); System.out.println(tradeXml); Trade trade = getTradeFromXml(tradeXml); assertEquals(trade.getId(), id); }
Reference: Maven Integration Testing And Spring Restful Services from our JCG partner John Dobie at the Agile Engineering Techniques blog.