Enterprise Java

Maven Integration Testing And Spring Restful Services

Introduction

My original blog showed how to seperate maven unit and integration tests using a very simple example. http://johndobie.blogspot.com/2011/06/seperating-maven-unit-integration-tests.html Since then a lot of people asked me for a more realistic example than the one used originally. This post shows how you split your unit and integration tests using the original method in a realistic environment where the application is actually deployed to a server.
  • 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
This article is aimed squarely at showing how to use Maven in a realistic way to start and deploy a set of services to a running server, before running your integration tests. It is not about the subtle details of REST or Spring MVC. I’ll cover this lightly enough to build a working application whilst providing references to more in depth articles for those that want more details.

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.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button