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

No comments:

Post a Comment