Showing posts with label unit. Show all posts
Showing posts with label unit. Show all posts

Saturday, January 5, 2019

Spring MVC: unit and integration tests

1.Intro 

Spring MVC is the one of most popular framework for REST services in java. Let's see how can we test our Spring MVC rest serves.

2. Project structure

I created simple gradle spring boot project. It has beside main app starter  just one rest controller (UserController), two domain entities (Greeting, User) and test: unit and integration.





3. build.gradle

Beside spring-boot dependencies I just added Lombok to reduce some boilerplate code for domain objects. 

buildscript {
   ext {
      springBootVersion = '2.1.1.RELEASE'   }
   repositories {
      mavenCentral()
   }
   dependencies {
      classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
   }
}

apply plugin: 'java'apply plugin: 'eclipse'apply plugin: 'org.springframework.boot'apply plugin: 'io.spring.dependency-management'
group = 'com.demien'version = '0.0.1-SNAPSHOT'sourceCompatibility = 1.8
repositories {
   mavenCentral()
}


dependencies {
   implementation('org.springframework.boot:spring-boot-starter-web')
   compileOnly('org.projectlombok:lombok')
   testImplementation('org.springframework.boot:spring-boot-starter-test')
}

4. Main application starter

Nothing interesting is here. Just SpringApplication.run

package com.demien.sprmvc;
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class SprmvcApplication {

   public static void main(String[] args) {
      SpringApplication.run(SprmvcApplication.class, args);   }

}



5. Domain objects

Thanks to Lombok, my domain objects are really tiny!

 package com.demien.sprmvc.domain;

import java.util.Date;
import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;
@Getter@Setter@NoArgsConstructor@AllArgsConstructorpublic class Greeting {
   private String message;   private Date dt;}


package com.demien.sprmvc.domain;
import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;
@Getter@Setter@AllArgsConstructor@NoArgsConstructorpublic class User {
   private String name;}

6. Rest controller

Finally we've reached something interesting - the controller we are going to test.
I created several methods:  rest endpoints, from simple to complex:
 - "hello" - GET which is jest returning text result
 - "hello-with-object" - GET but it's returning java object. So object should be serialized and returned as JSON
- "hello-with-parameter" - GET which has a path paameter
- "helo-post" - POST which is receiving java object and returning java object as well. Of course these objects will be serialized to JSON. 



package com.demien.sprmvc.controller;
import java.net.URI;import java.util.Date;
import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.demien.sprmvc.domain.Greeting;import com.demien.sprmvc.domain.User;
@Controllerpublic class UserController {

   private static final String helloWorldTemplate = "Hello World, %s!";   private int id = 1;
   @RequestMapping(value = "/hello")
   public @ResponseBody String hello() {
      return "Hello world!";   }

   @GetMapping("/hello-with-object")
   public @ResponseBody Greeting helloWithObject() {
      return new Greeting("Hello World", new Date());   }

   @GetMapping("/hello-with-parameter/name/{name}")
   public @ResponseBody Greeting helloWithParameter(@PathVariable String name) {
      return new Greeting(String.format(helloWorldTemplate, name), new Date());   }

   @PostMapping("/hello-post")
   public ResponseEntity<?> postTest(@RequestBody User user) {
      Greeting result = new Greeting(String.format(helloWorldTemplate, user.getName()), new Date());      URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id++).toUri();      return ResponseEntity.created(location).body(result);   }

}


7. Unit test

And now let's check if our controller works as expected. For unit tests we're using MockMVC. And also we need ObjectMapper for JSON serialization.
Please pay attention on this  annotation:

@WebMvcTest(UserController.class)

- our mockMvc will be created for UserController class. 


package com.demien.sprmvc.controller;
import static org.hamcrest.Matchers.containsString;import static org.hamcrest.Matchers.equalTo;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;import org.springframework.http.MediaType;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.test.web.servlet.MockMvc;import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import com.demien.sprmvc.domain.User;import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

   @Autowired   private MockMvc mvc;
   @Autowired   private ObjectMapper mapper;
   @Test   public void helloTest() throws Exception {
      mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
            .andExpect(content().string(equalTo("Hello world!")));   }

   @Test   public void helloWithObjectTest() throws Exception {
      mvc.perform(MockMvcRequestBuilders.get("/hello-with-object").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk()).andExpect(content().string(containsString("Hello World")));   }

   @Test   public void helloWithParameterTest() throws Exception {
      mvc.perform(MockMvcRequestBuilders.get("/hello-with-parameter/name/Buddy").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk()).andExpect(content().string(containsString("Hello World, Buddy")));   }

   @Test   public void postTest() throws Exception {
      User user = new User("Joe");      String userJson = mapper.writeValueAsString(user);      mvc.perform(
            MockMvcRequestBuilders.post("/hello-post").content(userJson).contentType(MediaType.APPLICATION_JSON))
            .andExpect(status().isCreated()).andExpect(content().string(containsString("Hello World, Joe")));   }

}


Results:



8. Integration test

For integration test we can not use any mocks - just real rest services. So we have to start our application
@SpringBootTest(classes = SprmvcApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

and  test needed rest enpoints using TestRestTemplate.

package com.demien.sprmvc;
import static org.hamcrest.MatcherAssert.assertThat;import static org.hamcrest.Matchers.containsString;import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.web.client.TestRestTemplate;import org.springframework.boot.web.server.LocalServerPort;import org.springframework.http.ResponseEntity;import org.springframework.test.context.junit4.SpringRunner;
import com.demien.sprmvc.domain.Greeting;import com.demien.sprmvc.domain.User;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SprmvcApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

public class SprmvcApplicationIT {

   private static final String LOCAL_HOST = "http://localhost:";
   @LocalServerPort   private int port;   private TestRestTemplate template = new TestRestTemplate();
   @Test   public void helloTest() throws Exception {
      ResponseEntity<String> response = template.getForEntity(createURL("/hello"), String.class);      assertThat(response.getBody(), equalTo("Hello world!"));   }

   private String createURL(String uri) {
      return LOCAL_HOST + port + uri;   }

   @Test   public void helloWithObjectTest() throws Exception {
      ResponseEntity<String> response = template.getForEntity(createURL("/hello-with-object"), String.class);      assertThat(response.getBody(), containsString("Hello World"));   }

   @Test   public void helloWithParameterTest() throws Exception {
      ResponseEntity<String> response = template.getForEntity(createURL("/hello-with-parameter/name/Buddy"),            String.class);      assertThat(response.getBody(), containsString("Hello World, Buddy"));   }

   @Test   public void postTest() throws Exception {
      User userBean = new User("Joe");      ResponseEntity<Greeting> response = template.postForEntity(createURL("/hello-post"), userBean, Greeting.class);      Greeting result = response.getBody();      assertThat(result.getMessage(), containsString("Hello World, Joe"));   }

}


Results:




9. The end 

Unit and integration tests for out rest controller are in place, so we're good :)
Full source code can be downloaded from here.

Monday, July 7, 2014

Rest(SpringMVC) application with XML-less Spring, Hibernate and embedded jetty, Unit and Integration tests

Not so long ago I was asked to write test application for working with Loans. Conditions :
1. XML-less spring
2. SpringMVC for rests
3. Two entity in application:Loan and User. Loan Can be extended with rate 1.5*previous rate. In case of more than 3 operation from one IP - reject operation.
4. Code have to be covered by unit and integration tests with ability for being executed from command line.

May be it will be useful for somebody, so I decided to publish it. Created application is very big so in post I will show only main classes, but entire application can be downloaded by link provided at the end of the post.

1. pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.demien</groupId>
    <artifactId>springmvcrest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring MVC Rest example</name>

    <properties>
        <spring.version>3.2.0.RELEASE</spring.version>
        <jetty.version>8.1.15.v20140411</jetty.version>
        <slf4j.version>1.7.5</slf4j.version>
        <hibernate.version>4.2.5.Final</hibernate.version>
        <h2.version>1.3.173</h2.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <integration.test.package>**/com/demien/springmvcrest/integration/test/**</integration.test.package>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0</version>
        </dependency>
        <!-- HIBERNATE -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <!-- H2 DB -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>${h2.version}</version>
        </dependency>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>       

        <!-- NOXML Sping library -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2</version>
        </dependency>

        <!-- Jetty embedded -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-servlet</artifactId>
            <version>${jetty.version}</version>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${slf4j.version}</version>
        </dependency>

        <!-- JSON marshaller -->
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-jaxrs</artifactId>
            <version>1.9.11</version>
        </dependency>

        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.11</version>
        </dependency>

        <!-- Testing -->

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.3.3</version>
        </dependency>

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.9.5</version>
        </dependency>

    </dependencies>


    <build>
        <finalName>springmvcrest</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.5</version>
 
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.13</version>
                <configuration>
                    <includes>
                        <include>**/*IT.java</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>failsafe-integration-tests</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

    <profiles>
        <profile>
            <id>integration</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-surefire-plugin</artifactId>
                        <version>2.5</version>
                        <configuration>
                            <excludes>
                                <exclude>**/*$*</exclude>
                            </excludes>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>

</project>


2. domains

Domains objects are very simple, so only one(of two) will be shown.

First, simple interface for entities which have to be stored in DB:
public interface IPersistable {
    Integer getId();
    void setId(Integer id);
}

Loan entity:
package com.demien.springmvcrest.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Entity;

@Entity(name = "LOAN")
public class Loan implements Serializable, IPersistable {

    public static enum STATE {
        OPEN, CLOSED, EXTENDED
    }

    private static final long serialVersionUID = 7566184847780781908L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private Integer id;

    @Column(name = "IP_ADDR")
    private String ipAddr;

    @Column(name = "AMOUNT")
    private Float amount;

    @Column(name = "RATE")
    private Float rate;

    @Column(name = "STATE")
    private String state;

    @Column(name = "DEADLINE")
    private Date deadLine;

    @Column(name = "PARENT_ID")
    private Integer parentId;

    @Column(name = "CREATE_DATE")
    private Date createDate;

    @Column(name = "CHANGE_DATE")
    private Date changeDate;
    
    @ManyToOne(cascade=CascadeType.ALL)
    @JoinColumn(name= "USER_ID")
    private User user;

    public Loan() {
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getIpAddr() {
        return ipAddr;
    }

    public void setIpAddr(String ipAddr) {
        this.ipAddr = ipAddr;
    }

    public Float getAmount() {
        return amount;
    }

    public void setAmount(Float amount) {
        this.amount = amount;
    }

    public Float getRate() {
        return rate;
    }

    public void setRate(Float rate) {
        this.rate = rate;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public Date getDeadLine() {
        return deadLine;
    }

    public void setDeadLine(Date deadLine) {
        this.deadLine = deadLine;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getChangeDate() {
        return changeDate;
    }

    public void setChangeDate(Date changeDate) {
        this.changeDate = changeDate;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Loan clone() {
        Loan loan = new Loan();
        loan.setAmount(new Float(this.getAmount()));
        loan.setDeadLine(new Date(this.getDeadLine().getTime()));
        loan.setIpAddr(new String(this.getIpAddr()));
        if (this.getParentId() != null) {
            loan.setParentId(new Integer(this.getParentId()));
        }
        loan.setRate(new Float(this.getRate()));
        loan.setState(new String(this.getState()));
        loan.setCreateDate(new Date());
        return loan;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Loan)) {
            return false;
        } else {
            Loan loan = (Loan) object;
            if (loan.getId().equals(this.getId())
                    && loan.getIpAddr().equals(this.getIpAddr())
                    && loan.getCreateDate().equals(this.getCreateDate())) {
                return true;
            }
            return false;
        }
    }

    @Override
    public String toString() {
        return "Loan [id=" + id + ", ipAddr=" + ipAddr + ", amount=" + amount
                + ", rate=" + rate + ", state=" + state + ", deadLine="
                + deadLine + ", parentId=" + parentId + ", createDate="
                + createDate + ", changeDate=" + changeDate + "]";
    }
    
    @Override
    public int hashCode() {
        return getId();
    }
    

}

3. DAO

- Used generic dao, like in my previous posts.

3.1 Interface

package com.demien.springmvcrest.dao;

import java.util.List;
import java.util.Map;

public interface IBaseDAO<T> {
    public T get(Integer id);
    public T save(T object);
    public void update(T object);
    public void delete(T object);
    public List<T> query(String hsql, Map<String, Object> params);

}

3.2 Implementation

package com.demien.springmvcrest.dao;

import java.util.List;
import java.util.Map;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class BaseDAOImpl<T> implements IBaseDAO<T> {
    private SessionFactory sessionFactory;
    private Class<T> cl;

    public BaseDAOImpl(Class<T> cl, SessionFactory sessionFactory) {
        this.cl=cl;
        this.sessionFactory = sessionFactory;
    }

    @Override
    public T get(Integer id) {
        Session session = sessionFactory.getCurrentSession();
        @SuppressWarnings("unchecked")
        T element = (T) session.get(cl, id);
        return element;
    }

    @SuppressWarnings("unchecked")
    @Override
    public T save(T object) {
        Session session = sessionFactory.getCurrentSession();
        T result=(T)session.save(object);
        return result;
    }

    @Override
    public void update(T object) {
        Session session = sessionFactory.getCurrentSession();
        session.update(object);
    }

    @Override
    public void delete(T object) {
        Session session = sessionFactory.getCurrentSession();
        session.delete(object);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> query(String hsql, Map<String, Object> params) {
        Session session = sessionFactory.getCurrentSession();
        Query query = session.createQuery(hsql);
        if (params != null) {
            for (String i : params.keySet()) {
                query.setParameter(i, params.get(i));
            }
        }

        List<T> result = null;
        if ((hsql.toUpperCase().indexOf("DELETE") == -1)
                && (hsql.toUpperCase().indexOf("UPDATE") == -1)
                && (hsql.toUpperCase().indexOf("INSERT") == -1)) {
            result = query.list();
        } else {
            query.executeUpdate();
        }
        return result;
    }

}


4. Service level

The same as in y previous posts: BaseService(interface and implementation) for common actions and "specific" services for  "specific" actions related with entities - only one of two will be shown.

4.1 BaseService interface

package com.demien.springmvcrest.service;

import java.util.List;

import com.demien.springmvcrest.dao.IBaseDAO;

public interface IBaseService<T> extends IBaseDAO<T> {
  List<T> getAll();
  void deleteAll();
}

4.2 BaseService implementation

package com.demien.springmvcrest.service;

import java.util.List;
import java.util.Map;

import org.springframework.transaction.annotation.Transactional;

import com.demien.springmvcrest.dao.IBaseDAO;

public abstract class BaseServiceImpl<T> implements IBaseService<T> {
    
    private IBaseDAO<T> dao;
    private Class<T> cl;
    
    
    public BaseServiceImpl(Class<T> cl) {
        this.cl=cl;
    }
    
    public void setDao(IBaseDAO<T> dao) {
        this.dao=dao;
    }
    
    
    @Transactional
    @Override
    public T get(Integer id) {
        return (T) dao.get(id);
    }

    @Transactional
    @Override
    public T save(T object) {
        T result=(T)dao.save(object);
        return result;
    }

    @Transactional
    @Override
    public void update(T object) {
        dao.update(object);     
    }

    @Transactional
    @Override
    public void delete(T object) {
        dao.delete(object);     
    }

    @Transactional
    @Override
    public List<T> query(String hsql, Map<String, Object> params) {
        return (List<T>)dao.query(hsql, params);
    }

    @Transactional
    @Override
    public List<T> getAll() {
        return query("from "+cl.getName(), null);
    }

    @Transactional
    @Override
    public void deleteAll() {
        query("delete from "+cl.getName(),null);
        
    }

}


4.2 Loan service interface - added specific to Loan entity methods:


package com.demien.springmvcrest.service;

import com.demien.springmvcrest.domain.Loan;

public interface ILoanService extends IBaseService<Loan> {
    
    void extendLoan(Integer id);

    String checkLoan(final String ipAddr, final Float amount);

    void setCheckHourFrom(final int checkHourFrom);

    void setCheckHourTo(final int checkHourTo);
}

4.3 Loan Service implementation

package com.demien.springmvcrest.service;

import static com.demien.springmvcrest.AppConst.CHECK_MAX_AMOUNT;
import static com.demien.springmvcrest.AppConst.CHECK_MAX_IP_DAY_REQUESTS;
import static com.demien.springmvcrest.AppConst.REJECT_REASON_BAD_TIME;
import static com.demien.springmvcrest.AppConst.REJECT_REASON_IP_DAY_ATTEMPTS;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.demien.springmvcrest.AppConst;
import com.demien.springmvcrest.dao.IBaseDAO;
import com.demien.springmvcrest.domain.Loan;

@Service
public class LoanServiceImpl extends BaseServiceImpl<Loan> implements ILoanService {
    
    public final static String QUERY_IP_COUNT="from "+Loan.class.getName()+" where ipAddr=:ipAddr and createDate>=:dateFrom and createDate<=:dateTo";
    private static int checkHourFrom;
    private static int checkHourTo;

    public LoanServiceImpl() {
        super(Loan.class);
    }

    @Autowired
    @Qualifier("loanDAOImpl")
    public void setDao(final IBaseDAO<Loan> dao) {
        super.setDao(dao);    
    }
    

    public void setCheckHourFrom(final int checkHourFrom) {
        LoanServiceImpl.checkHourFrom = checkHourFrom;
    }


    public void setCheckHourTo(final int checkHourTo) {
        LoanServiceImpl.checkHourTo = checkHourTo;
    }


    @SuppressWarnings("deprecation")
    public boolean isTimeInHourInterval(final int from, final int to) {
        Date d = new Date();
        int hour = d.getHours();

        if (hour >= from && hour <= to) {
            return true;
        } else {
            return false;
        }

    }

    @SuppressWarnings("deprecation")
    public boolean isDateToday(final Date loanDate) {
        Date d = new Date();
        if (loanDate.getYear() == d.getYear()
                && loanDate.getMonth() == d.getMonth()
                && loanDate.getDay() == d.getDay()) {
            return true;
        } else {
            return false;
        }

    }

    public int getIpRequestTodayCount(final String ipAddr) {
        
        Map<String,Object> params=new HashMap<String,Object>(); 
        params.put("ipAddr", ipAddr);
        Date dateTo=new Date();
        Date dateFrom=new Date(dateTo.getTime()-AppConst.DAY_SECONDS);
        params.put("dateFrom", dateFrom);
        params.put("dateTo", dateTo);
        List<Loan> loans=query(QUERY_IP_COUNT, params);
        
        return loans.size();
    }

    @Transactional
    @Override
    public String checkLoan(final String ipAddr, final Float amount) {
        StringBuilder result = new StringBuilder();
        // 1 check by max amount and hours
        if (amount >= CHECK_MAX_AMOUNT && isTimeInHourInterval(checkHourFrom, checkHourTo)) {
            result.append(REJECT_REASON_BAD_TIME);
        }
        // 2 check by ip addr
        if (getIpRequestTodayCount(ipAddr) >= CHECK_MAX_IP_DAY_REQUESTS) {
            result.append(REJECT_REASON_IP_DAY_ATTEMPTS);
        }

        return result.toString();
    }    

    @Transactional
    @Override
    public void extendLoan(final Integer id) {
        Loan oldLoan=get(id);
        Loan newLoan=oldLoan.clone();
        oldLoan.setState(Loan.STATE.EXTENDED.toString());
        oldLoan.setChangeDate(new Date());

        newLoan.setRate(newLoan.getRate()*AppConst.EXTEND_RATE_MUL);
        Date d=new Date();
        d.setTime(d.getTime()+AppConst.EXTEND_INTERVAL);
        newLoan.setDeadLine(d);
        newLoan.setParentId(id);
        update(oldLoan);
        save(newLoan);
    }

}

5. Controllers

5.1 BaseController - for common CRUD operations on entities.

package com.demien.springmvcrest.controller;


import java.util.List;

import org.springframework.web.bind.annotation.RequestBody;
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 com.demien.springmvcrest.domain.IPersistable;
import com.demien.springmvcrest.service.IBaseService;
import com.demien.springmvcrest.utils.JsonHelper;
import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;

import static com.demien.springmvcrest.AppConst.*;

public class BaseController<T extends IPersistable> {

    private IBaseService<T> service;
    
    public static final String SERVICE_HELLO="sayhello";
    public static final String SERVICE_GET_ALL="getall";
    public static final String SERVICE_GETBYID="getbyid";
    public static final String SERVICE_ADD="add";
    public static final String SERVICE_UPDATE="update";
    public static final String SERVICE_DELETE="delete";
    
    
    private Class<T> cl;
    
    public BaseController(Class<T> cl) {
        this.cl=cl;
    }
    
    public void setService(IBaseService<T> service) {
        this.service = service;
    }


    @RequestMapping(method=RequestMethod.GET, value="/sayhello")
    @ResponseBody
    public String sayHello() {
        System.out.println("Executing sayHello");
        return HELLO;
    }
    
    
    @RequestMapping(method=RequestMethod.GET, value="/getall")
    public @ResponseBody String getAll() throws JsonHelperException  {
        List<T> list = service.getAll();
        String json=JsonHelper.object2json(list);
        return json;
    }

    private T getEntityById(Integer id) {
        
        return service.get(id);
        
    }

    @RequestMapping(method=RequestMethod.GET, value="/getbyid")
    public @ResponseBody String getEntity(@RequestParam("id") Integer id) throws JsonHelperException {
        T result =service.get(id);
        String resultJson=JsonHelper.object2json(result);
        return resultJson;
    }
    
        protected T addEntity(T entity) {
        Object o=service.save(entity);
        if (o instanceof Integer) {
            Integer id=(Integer)o;
            entity.setId(id);
        }
        return entity;
    }

    @SuppressWarnings("unchecked")  
    @RequestMapping(method=RequestMethod.POST, value="/add")
    public @ResponseBody String addEntity(@RequestBody String json) throws JsonHelperException  {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        
        if (entity==null) {
            throw new RuntimeException("addEnity Error: entity is null");
        }
        
        T searchResult = getEntityById(entity.getId());
        if (searchResult == null) {
            entity=addEntity(entity);
        }
        String resultJson=JsonHelper.object2json(entity);
        return resultJson;
    }
    
    protected void updateEntity(T entity) {
        service.update(entity); 
    }

    @SuppressWarnings("unchecked")
    @RequestMapping(method=RequestMethod.POST, value="/update")
    public @ResponseBody String updateEntity(@RequestBody String json) throws JsonHelperException {     
        T entity=(T)JsonHelper.Json2Object(json, cl);
        updateEntity(entity);
        String resultJson=JsonHelper.object2json(entity);
        return resultJson;
    }
    
    protected void deleteEntity(T entity) {
        service.delete(entity);
    }

    @SuppressWarnings("unchecked")
    @RequestMapping(method=RequestMethod.POST, value="/delete")
    public @ResponseBody void deleteEntity(@RequestBody String json) throws JsonHelperException {
        T entity=(T)JsonHelper.Json2Object(json, cl);
        deleteEntity(entity);
    }

}

5.2. "specific" controller example  - Loan Controller.

package com.demien.springmvcrest.controller;

import static com.demien.springmvcrest.AppConst.*;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;
import com.demien.springmvcrest.service.ILoanService;
import com.demien.springmvcrest.service.IUserService;
import com.demien.springmvcrest.utils.JsonHelper;
import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;

@Controller
@RequestMapping(value = "/loan")
public class LoanController extends BaseController<Loan> {
    
    private ILoanService loanService;
    private IUserService userService;


    // services
    public static final String SERVICE_MAIN = "loan";
    public static final String SERVICE_CREATE_LOAN = "createLoan";
    public static final String SERVICE_EXTEND_LOAN = "extendLoan";

    public LoanController() {
        super(Loan.class);        
    }

    @Autowired
    public void setLoanService(ILoanService loanService) {
        this.loanService = loanService;
        super.setService(loanService);
    }

    @Autowired
    public void setUserService(IUserService userService) {
        this.userService = userService;
    }

    @RequestMapping(method=RequestMethod.GET, value="/createLoan")
    public @ResponseBody String createLoan(@RequestParam final String amount,
            @RequestParam final String date,
            @RequestParam final Integer userId, HttpServletRequest request) {
        String ipAddr = "";
        ipAddr=request.getRemoteAddr();

        Loan loan = new Loan();
        // checks for input params
        StringBuilder error = new StringBuilder();
        if (amount == null || amount.length() < 1) {
            error.append(ERROR_AMOUNT_IS_NULL);
        }
        if (date == null || amount.length() < 1) {
            error.append(ERROR_DATE_IS_NULL);
        }
        try {
            loan.setAmount(Float.parseFloat(amount));
        } catch (Exception e) {
            error.append(ERROR_WRONG_AMOUNT_FORMAT);
        }
        try {
            loan.setDeadLine(FORMATTER.parse(date));
        } catch (Exception e) {
            error.append(ERROR_WRONG_DATE_FORMAT);
        }

        User user = userService.get(userId);
        if (user == null) {
            error.append(ERROR_WRONG_USER_ID);
        }

        // main check procedure
        error.append(loanService.checkLoan(ipAddr, loan.getAmount()));

        if (error.length() == 0) {
            loan.setRate(DEFAULT_RATE);
            loan.setIpAddr(ipAddr);
            loan.setState(Loan.STATE.OPEN.toString());
            loan.setCreateDate(new Date());
            loan.setUser(user);
            addEntity(loan);
            return RESULT_OK;
        } else {
            return RESULT_REJECTED + error.toString();
        }
    }
    
    @RequestMapping(method=RequestMethod.GET, value="/extendLoan")
    public @ResponseBody String extendloan(@RequestParam("id") final Integer id) throws JsonHelperException {
        String json=getEntity(id);
        Loan loan=(Loan) JsonHelper.Json2Object(json, Loan.class);
        Date now=new Date();
        if (loan.getDeadLine().getTime()<now.getTime()) {
            return ERROR_EXTEND_DEADLINE_EXCEED;
        }
        loanService.extendLoan(id);
        return RESULT_EXTENDED;
    }

}

6. Configuration

6.1. Main config

package com.demien.springmvcrest.config;

import java.util.Properties;

import org.hibernate.SessionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import com.demien.springmvcrest.dao.BaseDAOImpl;
import com.demien.springmvcrest.dao.IBaseDAO;
import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;


@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "com.demien.springmvcrest")
public class AppConfig {

    // ---------- DAO ------------------
    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        Properties p = new Properties();
        dataSource.setConnectionProperties(p);
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:~/test");
        dataSource.setUsername("sa");
        dataSource.setPassword("sa");
        return dataSource;
    }

    @SuppressWarnings("rawtypes")
    @Bean
    public LocalSessionFactoryBean sessionFactoryBean() {
        LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource());
        Class[] annotatedClasses = new Class[] { User.class, Loan.class };
        sessionFactoryBean.setAnnotatedClasses(annotatedClasses);
        Properties p = new Properties();
        p.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        p.put("hibernate.show_sql", "true");
        p.put("hibernate.hbm2ddl.auto", "create");
        sessionFactoryBean.setHibernateProperties(p);
        return sessionFactoryBean;
    }

    @Bean
    public SessionFactory sessionFactory() {
        return sessionFactoryBean().getObject();
    }

    @Bean
    public HibernateTransactionManager txManager() {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(sessionFactory());
        return txManager;
    }

    @Bean
    public TransactionAttributeSource annotationTransactionAttributeSource() {
        return new AnnotationTransactionAttributeSource();
    }

    @Bean
    public IBaseDAO<Loan> loanDAOImpl() {
        IBaseDAO<Loan> loanDAOImpl = new BaseDAOImpl<Loan>(Loan.class,
                sessionFactory());
        return loanDAOImpl;
    }

    @Bean
    public IBaseDAO<User> userDAOImpl() {
        IBaseDAO<User> userDAOImpl = new BaseDAOImpl<User>(User.class,
                sessionFactory());
        return userDAOImpl;
    }


}


6.2. Configuration for enabling MVC

package com.demien.springmvcrest.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;


@Configuration
@EnableWebMvc
@Profile("container")
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    
}


7. Utilities

7.1 JsonJelper - for converting Object->Json->Object

package com.demien.springmvcrest.utils;

import java.util.List;
import org.codehaus.jackson.map.ObjectMapper;

public class JsonHelper {
    
    private static ObjectMapper mapper = new ObjectMapper(); 
    
    @SuppressWarnings("rawtypes")
    public static Object Json2Object(String json, Class cl) throws JsonHelperException {    
        return Json2ObjectMain(json, cl, false);
    }   
    
    @SuppressWarnings("rawtypes")
    public static Object Json2ObjectList(String json, Class cl) throws  JsonHelperException{
        return Json2ObjectMain(json, cl, true);
    }
    
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private static Object Json2ObjectMain(String json, Class cl, boolean asList) throws JsonHelperException {
        if (json==null || json.length()==0) {
            return null;
        }
        Object result=null;
        try {
            if (asList) {
                result=mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, cl));
            } else {
                result=mapper.readValue(json, cl);  
            }
        } catch (Exception e) {
            throw new JsonHelper.JsonHelperException("Exception in Json2Object. Exception:"+e.getMessage()+". JSON="+json);
        }
        
        return result;
    }
    
    public static String object2json(Object object) throws JsonHelperException {
        String result="";
        try {
           result=mapper.writeValueAsString(object);
        } catch (Exception e){
            throw new JsonHelper.JsonHelperException("Exception in object2json. Exception:"+e.getMessage()+".");
        }
        return result;
    }
    
    public static class JsonHelperException extends Exception {
        
        private static final long serialVersionUID = -7568626836534766197L;

        public JsonHelperException(String message) {
            super(message);
        }
    }
}

7.2. Object data populator - for tests - fill object fields with random values

package com.demien.springmvcrest.utils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import com.demien.springmvcrest.domain.IPersistable;


public class ObjectDataPopulator {

    public static Object populate(final IPersistable instance)
            throws Exception {
        instance.setId(getRandomInteger());
        List<Field> fields = getAllFields(instance);
        for (Field eachField : fields) {
            eachField.setAccessible(true);
            if (eachField.getType().equals(Integer.class)) {
                eachField.set(instance, getRandomInteger());
            } else if (eachField.getType().equals(String.class)) {
                eachField.set(instance, getRandomString());
            } else if (eachField.getType().equals(Float.class)) {
                eachField.set(instance, getRandomFloat());
            } else if (eachField.getType().equals(Date.class)) {
                eachField.set(instance, getRandomDate());   
            } else if (eachField.getType().equals(Set.class)) { 
             // here should be generators for other standard types like Float, Long....
            } else {
                // processing aggregated objects
                if (eachField.getType().newInstance() instanceof IPersistable) {
                    //eachField.set(instance, populate((IPersistable) eachField.getType().newInstance()));
                    
                }                   
            }
        }

        return instance;

    }

    private static Integer getRandomInteger() {
        return new Integer((int) (Math.random() * 1000));
    }
    
    private static Float getRandomFloat() {
        return new Float( Math.random() * 1000+Math.random());
    }
    
    private static Date getRandomDate() {
        return new Date(  new Date().getTime() - (int)(Math.random() * 1000*60*60*24*100) );
    }

    private static String getRandomString() {
        StringBuffer result = new StringBuffer();
        String[] letters = new String[] { "A", "B", "C", "D", "E", "F", "G" };
        int length = (int) (Math.random() * 15) + 5;
        for (int i = 0; i < length; i++) {
            int pos = (int) (Math.random() * letters.length);
            result.append(letters[pos]);
        }
        return result.toString();
    }

    private static List<Field> getAllFields(final Object instance) {
        Field[] fields = instance.getClass().getDeclaredFields();
        List<Field> result = new ArrayList<Field>();
        for (int i = 0; i < fields.length; i++) {
            if (!java.lang.reflect.Modifier.isFinal(fields[i].getModifiers())
                    && !java.lang.reflect.Modifier.isStatic(fields[i]
                            .getModifiers())) {
                result.add(fields[i]);
            }
        }
        return result;
    }
}


7.3. RestTestClient - client for calling rest methods and transform results into proper objects

package com.demien.springmvcrest.utils;

import java.io.BufferedReader;

import org.apache.http.NameValuePair;

import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;

import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;

public class RestTestClient {

    public static final int RESPONSE_OK = 200;

    public String getResponseData(HttpResponse response)
            throws Exception {
        StringBuilder result = new StringBuilder();
        try {
            BufferedReader rd = new BufferedReader(new InputStreamReader(
                    response.getEntity().getContent()));
            String line = "";
            while ((line = rd.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            throw new Exception("Exception in getResponseData:"
                    + e.getMessage() + ".");
        }
        return result.toString();
    }

    @SuppressWarnings("rawtypes")
    public Object Json2Object(String src, Class cl, boolean asList)
            throws JsonHelperException {
        Object result = null;
        if (asList) {
            result = JsonHelper.Json2ObjectList(src, cl);
        } else {
            result = JsonHelper.Json2Object(src, cl);
        }
        return result;
    }

    public HttpResponse sendGet(String url) throws ClientProtocolException,
            IOException {
        HttpClient client = HttpClientBuilder.create().build();
        HttpGet get = new HttpGet(url);
        get.setHeader(
                "Accept",
                "text/html,application/xhtml+xml,application/xml,application/json;q=0.9,*/*;q=0.8");
        get.setHeader("Content-Type", "application/json");
        HttpResponse response = client.execute(get);

        return response;
    }

    public String sendGetStringResult(String url) throws Exception {
        return getResponseData(sendGet(url));
    }

    @SuppressWarnings("rawtypes")
    public Object sendGetObjectResult(String url, Class cl) throws Exception {
        return sendGetObjectResult(url, cl, false);
    }

    @SuppressWarnings("rawtypes")
    public Object sendGetObjectResult(String url, Class cl, boolean asList)
            throws Exception {
        String src = sendGetStringResult(url);
        Object result = null;
        if (asList) {
            result = JsonHelper.Json2ObjectList(src, cl);
        } else {
            result = JsonHelper.Json2Object(src, cl);
        }
        return result;
    }
    
    public HttpResponse sendPost(String url, List<NameValuePair> postParams, Object object) throws Exception {
        HttpClient client = HttpClientBuilder.create().build();
        HttpPost post = new HttpPost(url);
        
        post.setHeader("Accept",
                "text/html,application/xhtml+xml,application/xml, application/json;q=0.9,*/*;q=0.8");
        
        if (postParams != null) {
            post.setEntity(new UrlEncodedFormEntity(postParams));
            post.setHeader("Content-Type", "application/x-www-form-urlencoded");
        }
        if (object!=null) {
            String json = JsonHelper.object2json(object);
            StringEntity input = new StringEntity(json);
            input.setContentType("application/json");
            post.setEntity(input);
            post.setHeader("Content-Type", "application/json");

        }

        HttpResponse response = client.execute(post);

        return response;        
    }

    public HttpResponse sendPost(String url, List<NameValuePair> postParams)
            throws Exception {
       return sendPost(url, postParams, null);
    }

    public HttpResponse sendPost(String url, Object object) throws Exception {
        return sendPost(url, null, object);
    }

    public String sendPostStringResult(String url,
            List<NameValuePair> postParams) throws IllegalStateException,
            IOException, Exception {
        return getResponseData(sendPost(url, postParams));
    }

    @SuppressWarnings("rawtypes")
    public Object sendPostObjectResult(String url,
            List<NameValuePair> postParams, Class cl)
            throws IllegalStateException, IOException, Exception {
        return sendPostObjectResult(url, postParams, cl, false);
    }

    @SuppressWarnings("rawtypes")
    public Object sendPostObjectResult(String url,
            List<NameValuePair> postParams, Class cl, boolean asArray)
            throws IllegalStateException, IOException, Exception {
        String src = sendPostStringResult(url, postParams);
        return Json2Object(src, cl, asArray);
    }

}

7.4. RestTestServer - starting application on embedded jetty server

package com.demien.springmvcrest.utils;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import java.io.IOException;

public class RestTestServer {

    public static final int TEST_PORT = 8080;
    public static final String TEST_CONTEXT = "/";
    private static final String CONFIG_LOCATION = "com.demien.springmvcrest.config";
    private static final String MAPPING_URL = "/*";
    private String mode;
    private Server server;

    public RestTestServer() {

    }

    public RestTestServer(final String mode) {
        this.mode = mode;
    }    

    public void start() throws Exception {
        //LOGGER.debug("Starting server at port :"+ TEST_PORT);
        server = new Server(TEST_PORT);
        server.setHandler(getServletContextHandler(getContext()));
        server.start();
        System.out.println("Server started at http://localhost:" + TEST_PORT + TEST_CONTEXT);
        //LOGGER.info("Server started at http://localhost:" + TEST_PORT + TEST_CONTEXT);
        if (mode == null || !mode.equals("JUNIT")) {
            server.join();
        }
     }
    
    public void stop() throws Exception {
        if (server != null) {
            server.stop();
            server.join();
            server.destroy();
            server = null;
        }
    }   

    private ServletContextHandler getServletContextHandler(WebApplicationContext context) throws IOException {
        ServletContextHandler contextHandler = new ServletContextHandler();
        contextHandler.setErrorHandler(null);
        contextHandler.setContextPath(TEST_CONTEXT);
        contextHandler.addServlet(new ServletHolder(new DispatcherServlet(context)), MAPPING_URL);
        contextHandler.addEventListener(new ContextLoaderListener(context));
        contextHandler.setResourceBase(new ClassPathResource("webapp").getURI().toString());
        return contextHandler;
    }

    private WebApplicationContext getContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //context.register(AppConfig.class);
        context.setConfigLocation(CONFIG_LOCATION);
        return context;
    }

}


8. Unit tests  - there are a lot of tests in application - only few of them will be shown.

8.1. BaseDAOTest


package com.demien.springmvcrest.dao;

import java.io.Serializable;

import junit.framework.Assert;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;

import com.demien.springmvcrest.dao.BaseDAOImpl;

import static org.mockito.Mockito.*;

public class BaseDAOTest {
    BaseDAOImpl<TestClass> dao;
    SessionFactory sessionFactory;
    Session session;
    TestClass testObject;
    
    private class TestClass implements Serializable {
        private static final long serialVersionUID = -8877252618686760948L;
    }
    
    @Before
    public void init() {
        testObject=new TestClass();
        
        sessionFactory=mock(SessionFactory.class);
        session=mock(Session.class);
        when(sessionFactory.getCurrentSession()).thenReturn(session);
        
        dao=new BaseDAOImpl<TestClass>(TestClass.class, sessionFactory);
    }
    
    @Test
    public void getTest() {
        Integer id=1;
        when(session.get(TestClass.class, id)).thenReturn(testObject);
        Object result=dao.get(id);
        Assert.assertEquals(testObject, result);
    }
    
    @Test
    public void saveTest() {
        TestClass newObject=new TestClass();
        when(session.save(testObject)).thenReturn(newObject);
        Object result=dao.save(testObject);
        Assert.assertEquals(newObject, result);
    }
    
    @Test
    public void updateTest() {
        dao.update(testObject);
        verify(session).update(testObject);
    }
    
    @Test
    public void deleteTest() {
        dao.delete(testObject);
        verify(session).delete(testObject);
    }
    
    @Test
    public void queryTest() {
        Query query=mock(Query.class);
        when(session.createQuery(anyString())).thenReturn(query);
        //1 
        dao.query("select * from dual", null);
        verify(query).list();
        //2 
        dao.query("delete from dual", null);
        verify(query).executeUpdate();   
    }
}

8.2. LoanController test

package com.demien.springmvcrest.rest;

import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import com.demien.springmvcrest.service.ILoanService;
import com.demien.springmvcrest.service.IUserService;
import com.demien.springmvcrest.service.LoanServiceImplTest;
import com.demien.springmvcrest.utils.JsonHelper.JsonHelperException;
import com.demien.springmvcrest.utils.ObjectDataPopulator;
import com.demien.springmvcrest.AppConst;
import com.demien.springmvcrest.controller.LoanController;
import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;

public class LoanControllerTest extends BaseControllerTest<Loan> {

    private static ILoanService loanService;
    private static IUserService userService;

    private LoanController controller;


    public LoanControllerTest() {
        super(Loan.class);
    }

    @Before
    public void init() {
        loanService=mock(ILoanService.class);
        userService=mock(IUserService.class);
        controller=new LoanController();
        controller.setLoanService(loanService);
        controller.setUserService(userService);
    }



    @Test
    public void createLoanTest () throws Exception {
        String ipAddr="127.0.0.1";
        HttpServletRequest request=mock(HttpServletRequest.class);
        when(request.getRemoteAddr()).thenReturn(ipAddr);
        
        User user=new User();
        ObjectDataPopulator.populate(user);
        Loan loan=new Loan();
        ObjectDataPopulator.populate(loan);
        when(userService.get(user.getId())).thenReturn(user);
        ArgumentCaptor<Loan> captor = ArgumentCaptor.forClass(Loan.class);
        String dateStr=AppConst.FORMATTER.format(loan.getDeadLine());
        when(loanService.checkLoan(ipAddr, loan.getAmount())).thenReturn("");
        controller.createLoan(loan.getAmount().toString(), dateStr, user.getId(), request);
        verify(userService).get(user.getId());
        verify(loanService).save(captor.capture());
        assertEquals(loan.getAmount(), captor.getValue().getAmount());
        assertEquals(dateStr, AppConst.FORMATTER.format(captor.getValue().getDeadLine()) );
        assertEquals(user.getId(), captor.getValue().getUser().getId());
    }

    @Test
    public void extendLoanTest() throws JsonHelperException {
        Integer id=666;
        
        Date d=new Date();
        Loan loan=LoanServiceImplTest.getTestLoan("", new Date());
        loan.setDeadLine(new Date(d.getTime()+AppConst.DAY_SECONDS));
        when(loanService.get(id)).thenReturn(loan);
        
        controller.extendloan(id);
        verify(loanService).extendLoan(id);
    }


}

9. Integration tests

9.1. Base Integration test - test for BaseController

package com.demien.springmvcrest.integration.test;

import static org.junit.Assert.*;

import java.io.IOException;
import java.util.List;

import org.apache.http.HttpResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import com.demien.springmvcrest.controller.BaseController;
import com.demien.springmvcrest.utils.ObjectDataPopulator;
import com.demien.springmvcrest.utils.RestTestClient;
import com.demien.springmvcrest.utils.RestTestServer;
import com.demien.springmvcrest.AppConst;
import com.demien.springmvcrest.domain.IPersistable;

public abstract class BaseIT<T extends IPersistable> {
    protected static RestTestServer server = new RestTestServer("JUNIT");
    protected static RestTestClient client = new RestTestClient();

    protected final String BASE_URL = "http://localhost:"
            + RestTestServer.TEST_PORT + RestTestServer.TEST_CONTEXT
            //+ "/services/rest/"
            ;
    protected final String SERVICE_URL;
    private Class<T> cl;

    public BaseIT(String sevicePrexif, Class<T> cl) {
        SERVICE_URL = BASE_URL + sevicePrexif + "/";
        this.cl=cl;
    }

    @BeforeClass
    public static void init() throws Exception {
        server.start();
    }

    @AfterClass
    public static void finish() throws Exception {
        server.stop();
    }
    
    private T getTestEntity() throws Exception {
      T entity = cl.newInstance();
      ObjectDataPopulator.populate(entity);
    return entity;
   }
    
    @Test
    public void sayHello() throws Exception {
        String value = client.sendGetStringResult(SERVICE_URL
                + BaseController.SERVICE_HELLO);
        System.out.println("value=" + value);
        assertEquals(AppConst.HELLO, value);
    }

    @SuppressWarnings("unchecked")
    private T getById(Integer id) throws Exception {
        T result = (T) client.sendGetObjectResult(SERVICE_URL
                + BaseController.SERVICE_GETBYID + "?id=" + id, cl);
        return result;
    }

    private void addEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseController.SERVICE_ADD, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    private void updateEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseController.SERVICE_UPDATE, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    private void deleteEntity(T entity) throws Exception {
        HttpResponse response = client.sendPost(SERVICE_URL
                + BaseController.SERVICE_DELETE, entity);
        int responseCode = response.getStatusLine().getStatusCode();
        assertEquals(RestTestClient.RESPONSE_OK, responseCode);
    }

    @SuppressWarnings({ "unchecked" })
    private List<T> getEntityList() throws IllegalStateException, IOException,
            Exception {

        List<T> result = (List<T>) client.sendGetObjectResult(SERVICE_URL
                + BaseController.SERVICE_GET_ALL, cl, true);
        return result;
    }

    @Test
    public void addEntityTest() throws Exception {
        int cnt=getEntityList().size();
        T testEntity = getTestEntity();
        addEntity(testEntity);
        assertEquals(cnt+1, getEntityList().size());
    }

    @Test
    public void updateEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);
        testEntity=getEntityList().get(0);
        Integer id = testEntity.getId();

        T forUpdate = getTestEntity();
        forUpdate.setId(id);
        updateEntity(forUpdate);

        T searchEntity = getById(id);
        assertEquals(forUpdate, searchEntity);
    }

    @Test
    public void deleteEntityTest() throws Exception {
        T testEntity = getTestEntity();
        addEntity(testEntity);
        Integer id = getEntityList().get(0).getId();
        testEntity.setId(id);

        deleteEntity(testEntity);

        T searchEntity = getById(id);
        assertTrue(searchEntity==null);
    }
}


9.2. Loan integration test- for Loan Controller

package com.demien.springmvcrest.integration.test;

import static com.demien.springmvcrest.AppConst.*;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;

import java.util.Date;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.test.context.web.WebAppConfiguration;

import com.demien.springmvcrest.controller.BaseController;
import com.demien.springmvcrest.controller.LoanController;
import com.demien.springmvcrest.service.ILoanService;
import com.demien.springmvcrest.service.IUserService;
import com.demien.springmvcrest.utils.ObjectDataPopulator;
import com.demien.springmvcrest.config.AppConfig;
import com.demien.springmvcrest.config.WebMvcConfig;
import com.demien.springmvcrest.domain.Loan;
import com.demien.springmvcrest.domain.User;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfig.class,WebMvcConfig.class}, loader = AnnotationConfigContextLoader.class)
@WebAppConfiguration
public class LoanIT extends BaseIT<Loan> {

    @Autowired
    private ILoanService loanService;

    @Autowired
    private IUserService userService;

    @Autowired
    private LoanController loanController;
    
    private final int DAY_SECONDS=1000*60*60*24;

    public LoanIT() {
        super(LoanController.SERVICE_MAIN, Loan.class);
    }

    @Before
    public void clear() throws Exception {
        loanService.deleteAll();
        userService.deleteAll();
        // create test user
        User user=new User();
        ObjectDataPopulator.populate(user);
        userService.save(user);
    }

    private Integer getTestUserId() {
        User user=userService.getAll().get(0);
        return user.getId();
    }

    @SuppressWarnings("unchecked")
    private List<Loan> getAllLoans() throws Exception {
        List<Loan> loans = (List<Loan>) client.sendGetObjectResult(SERVICE_URL
                +  BaseController.SERVICE_GET_ALL, Loan.class, true);
        assertNotNull(loans);
        return loans;
    }

    private String createLoan(final Float amount, final Date date, final Integer userId) throws Exception {
        String result=client.sendGetStringResult(SERVICE_URL+LoanController.SERVICE_CREATE_LOAN+"?amount="+amount+"&date="+FORMATTER.format(date)+"&userId="+userId);
        assertNotNull(result);
        return result;
    }

    @Test
    public void loanCreationSuccessfullTest() throws Exception {
        List<Loan> loans =getAllLoans();
        int cnt=loans.size();
        assertEquals(0, cnt);
        // create
        String result=createLoan(1f, new Date(), getTestUserId());
        assertEquals(result, RESULT_OK);
        loans =getAllLoans();
        assertEquals(1, loans.size());
        //check for user
        Loan loan=loans.get(0);
        assertEquals(getTestUserId(), loan.getUser().getId());
    }

    @Test
    public void loanCreationRejectedByMaxIpRequestTest() throws Exception {
        // create 3 requests from one ip
        for (int i=0; i<CHECK_MAX_IP_DAY_REQUESTS; i++) {
            String result=createLoan(1f, new Date(), getTestUserId());
            assertEquals(result, RESULT_OK);
        }
        // next should fail
        String result=createLoan(1f, new Date(), -666);
        assertTrue(result.contains(RESULT_REJECTED));
        assertTrue(result.contains(REJECT_REASON_IP_DAY_ATTEMPTS));
        assertTrue(result.contains(ERROR_WRONG_USER_ID));
    }

    @SuppressWarnings("deprecation")
    @Test
    public void loanCreationRejectedByBadTimeAndMaxAmountTest() throws Exception {
        // set "wrong time"
        Date d=new Date();
        loanService.setCheckHourFrom(d.getHours()-1);
        loanService.setCheckHourTo(d.getHours()+1);

        String result=createLoan(CHECK_MAX_AMOUNT+1, new Date(), getTestUserId());

        assertTrue(result.contains(RESULT_REJECTED));
        assertTrue(result.contains(REJECT_REASON_BAD_TIME));
    }

    @Test
    public void testLoanExtendSuccess() throws Exception {
        Date d=new Date();
        String result=createLoan(1f, new Date(d.getTime()+DAY_SECONDS ), getTestUserId());              
        assertEquals(result, RESULT_OK);
        List<Loan> loans =getAllLoans();
        int cnt=loans.size();
        Loan loan=loans.get(0);
        result=client.sendGetStringResult(SERVICE_URL+LoanController.SERVICE_EXTEND_LOAN+"?id="+loan.getId());
        assertEquals(RESULT_EXTENDED, result);
        loans =getAllLoans();
        assertEquals(cnt+1, loans.size());
        // check for correct ID/PARENT ID
        int cnt1=0;
        int cnt2=0;
        for (Loan eachLoan:loans) {
            if (eachLoan.getId().equals(loan.getId())) {
                cnt1++;
                assertEquals(Loan.STATE.EXTENDED.toString(), eachLoan.getState());
            }
            if (eachLoan.getParentId()!=null && eachLoan.getParentId().equals(loan.getId())) {
                cnt2++;
                assertEquals(Loan.STATE.OPEN.toString(), eachLoan.getState());
                assertTrue(eachLoan.getRate()>loan.getRate());
                assertTrue(eachLoan.getDeadLine().getTime()>loan.getDeadLine().getTime());
            }
        }
        assertEquals(1,cnt1);
        assertEquals(1,cnt2);

    }
    
    @Test
    public void testLoanExtendFail() throws Exception {
        int DAY_SECONDS=1000*60*60*24;
        Date d=new Date();
        String result=createLoan(1f, new Date(d.getTime()-DAY_SECONDS), getTestUserId());
        assertEquals(result, RESULT_OK);
        Loan loan=getAllLoans().get(0);
        result=client.sendGetStringResult(SERVICE_URL+LoanController.SERVICE_EXTEND_LOAN+"?id="+loan.getId());      
        assertEquals(ERROR_EXTEND_DEADLINE_EXCEED, result);
        // check for correct ID/PARENT ID
    }    

}




10. resume

Commands for lunching application from command line:
mvn test   - run unit tests
mvn integration-test - run integration tests

Full source code can be downloaded from here.