Friday, October 30, 2015

Spring boot with spring data and h2 database

1. Intro

In my previous posts I wrote about :
 - Spring data simple example
 - Spring boot simple example
 And now, it's time combine them together :)
It will be standard back-end for rest-based WEB application.  




For data access(DAO) it will use SpringData, for rest-part/integrationTesting  - SpringBoot, for DB - H2 database.


2.Aplication structure

Standart "maven based" application structure:
Main sources :

 

 Test sources :


3.Maven project file(pom.xml)

Here just SpringBoot dependencies for web,data,testing and H2 DB dependency.

<?xml version="1.0" encoding="UTF-8"?><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/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.demien</groupId>
   <artifactId>sboot</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>sboot</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.2.7.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->   </parent>

   <properties>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      <java.version>1.8</java.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      

      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>

      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <scope>runtime</scope>
      </dependency>
      
   </dependencies>
   
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
   

</project>

4. Domain objects : interace IPersistable

 In several part of application, especially in test, I needed to get ID from entity objects, so  I created interface for that purpose and all my domain entities had to implement it:

package com.demien.sboot.domain;

public interface IPersistable<ID> {
    ID getId();
    void setId(ID id);
}

4.1 Domain objects :  Category

Just regular POJO, with implementation of described above IPersistable interface.

package com.demien.sboot.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity(name = "CATEGORY")
public class Category implements IPersistable<Long>{

    @Id @GeneratedValue    @Column(name = "CATEGORY_ID")
    private Long categoryId;

    @Column(name="CATEGORY_NAME")
    private String categoryName;

    @Column(name = "CATEGORY_DESCRIPTION")
    private String categoryDescription;

    @Column(name="PARENT_CATEGORY_ID")
    private Long parentCategoryId;

    public Category() {}

    public Long getCategoryId() {
        return categoryId;
    }

    public void setCategoryId(Long categoryId) {
        this.categoryId = categoryId;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public String getCategoryDescription() {
        return categoryDescription;
    }

    public void setCategoryDescription(String categoryDescription) {
        this.categoryDescription = categoryDescription;
    }

    public Long getParentCategoryId() {
        return parentCategoryId;
    }

    public void setParentCategoryId(Long parentCategoryId) {
        this.parentCategoryId = parentCategoryId;
    }

    @Override    public String toString() {
        return "Category{" +
                "categoryId=" + categoryId +
                ", categoryName='" + categoryName + '\'' +
                '}';
    }

    @Override    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Category category = (Category) o;

        if (!categoryId.equals(category.categoryId)) return false;
        if (!categoryName.equals(category.categoryName)) return false;
        if (categoryDescription != null ? !categoryDescription.equals(category.categoryDescription) : category.categoryDescription != null)
            return false;
        return !(parentCategoryId != null ? !parentCategoryId.equals(category.parentCategoryId) : category.parentCategoryId != null);

    }

    @Override    public int hashCode() {
        return categoryName.hashCode();
    }

    @Override    public Long getId() {
        return categoryId;
    }

    @Override    public void setId(Long id) {
        setCategoryId(id);
    }
}

4.2 Domain objects :  Param

Just regular POJO, with implementation of described above IPersistable interface.

package com.demien.sboot.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity(name="PARAM")
public class Param implements IPersistable<Long> {

    @Id @GeneratedValue    @Column (name="PARAM_ID")
    private Long paramId;

    @Column(name = "PARAM_TYPE")
    private String ParamType;

    @Column(name="PARAM_NAME")
    private String paramName;

    @Column(name="PARAM_DESCRIPTION")
    private String paramDescription;

    @Column(name="PARAM_PROPERTIES")
    private String paramProperties;

    public Param() {
    }

    @Override    public Long getId() {
        return getParamId();
    }

    @Override    public void setId(Long paramId) {
        setParamId(paramId);
    }

    public Long getParamId() {
        return paramId;
    }

    public void setParamId(Long paramId) {
        this.paramId = paramId;
    }

    public String getParamType() {
        return ParamType;
    }

    public void setParamType(String paramType) {
        ParamType = paramType;
    }

    public String getParamName() {
        return paramName;
    }

    public void setParamName(String paramName) {
        this.paramName = paramName;
    }

    public String getParamDescription() {
        return paramDescription;
    }

    public void setParamDescription(String paramDescription) {
        this.paramDescription = paramDescription;
    }

    public String getParamProperties() {
        return paramProperties;
    }

    public void setParamProperties(String paramProperties) {
        this.paramProperties = paramProperties;
    }

    @Override    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Param param = (Param) o;

        if (!paramId.equals(param.paramId)) return false;
        if (!ParamType.equals(param.ParamType)) return false;
        return paramName.equals(param.paramName);

    }

    @Override    public int hashCode() {
        int result = paramId.hashCode();
        result = 31 * result + ParamType.hashCode();
        result = 31 * result + paramName.hashCode();
        return result;
    }
}


5.1 Repositories : CategoryRepository

Details of using Spring data I described in a post which I mentioned at the beginning. I'm just extending JpaRepository interface and adding one more method to it. 

package com.demien.sboot.repository;

import com.demien.sboot.domain.Category;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository(value = "CategoryRepository")
public interface CategoryRepository extends JpaRepository<Category, Long> {

    @Query("FROM CATEGORY where CATEGORY_NAME like %?1% ")
    List<Category> findByName(String name);
}

5.2 Repositories : ParamRepository

Here, even easier : all I need is a standard methods from JpaRepository :
package com.demien.sboot.repository;

import com.demien.sboot.domain.Param;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository(value = "ParamRepository")
public interface ParamRepository extends JpaRepository<Param, Long> {
}


6. Services

By common practice, business logic have to be separated from DB side and from REST side. That is why I(and not only I) have services in application. 
 

6.1 Services : AbstractService

Services will have a lot of common operation(save, getAll, getById), so I decided  to implement them only once (DRY - Don't Repeat Yourself).
package com.demien.sboot.service;

import org.springframework.data.jpa.repository.JpaRepository;

import java.io.Serializable;
import java.util.List;

public abstract class AbstractService<T,ID extends Serializable> {

    private JpaRepository<T,ID> repository;

    public AbstractService(JpaRepository<T,ID> repository) {
        this.repository=repository;

    }

    public T save(T entity) {
        T result=repository.save(entity);
        return result;
    }

    public void delete(T entity) {
        repository.delete(entity);
    }

    public T findOne(ID entityId) {
        T result=repository.findOne(entityId);
        return result;
    }

    public List<T> findAll() {
        List<T> result= repository.findAll();
        return result;
    }


}

 

6.2 Services : CategoryService

Now, then main operations moved into AbstractService class, we can just extends from it and add one specific for CategoryService method :
package com.demien.sboot.service;

import com.demien.sboot.domain.Category;
import com.demien.sboot.repository.CategoryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Servicepublic class CategoryService extends AbstractService<Category, Long> {

    private CategoryRepository repository;


    @Autowired    public CategoryService(CategoryRepository repository) {
        super(repository);
        this.repository=repository;
    }

    public List<Category> findByName(String name) {
        return repository.findByName(name);
    }
}

  

6.3 Services : ParamService

Here - we don't have specific methods, so extending will be enough for us :
package com.demien.sboot.service;

import com.demien.sboot.domain.Param;
import com.demien.sboot.repository.ParamRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Servicepublic class ParamService extends AbstractService<Param, Long> {

    private ParamRepository paramRepository;

    @Autowired    public ParamService(ParamRepository paramRepository) {
        super(paramRepository);
        this.paramRepository=paramRepository;

    }

}
 

7. Controllers 

Controllers -  interface of our application  - they will provide rest services(URLs) for client(HTML5 based UI).


7.1 Controllers : AbstractController

Just like for Services, all common logic were separated into Parent class.
Here used SpringBoot for annotating methods which will be used as rest-services.   Business logic are in service-classes, so every controller has corresponding service object. 

package com.demien.sboot.rest;

import com.demien.sboot.service.AbstractService;
import org.springframework.web.bind.annotation.*;

import java.io.Serializable;
import java.util.List;

public abstract class AbstractController<T, ID extends Serializable> {

    private AbstractService<T,ID> service;

    public AbstractController(AbstractService<T,ID> service) {
        this.service=service;
    }

    @RequestMapping("/hello")
    public String sayHello() {
        return "Hello from controller:"+ this.getClass();
    }

    @RequestMapping(value = "save",  method = RequestMethod.POST)
    public @ResponseBody    T save(@RequestBody T entity) {
        T result=service.save(entity);
        return result;
    }

    @RequestMapping(value="findOne/{entityId}", method = RequestMethod.GET)
    public T findOne(@PathVariable ID entityId) {
        return service.findOne(entityId);
    }

    @RequestMapping(value="findAll", method = RequestMethod.GET)
    public List<T> findAll() {
        return service.findAll();
    }

    @RequestMapping(value = "delete",  method = RequestMethod.POST)
    public @ResponseBody    void delete(@RequestBody T entity) {
        service.delete(entity);
    }


}

7.2 Controllers : CategoryController

The same as in previous steps : extending and adding one method.
package com.demien.sboot.rest;

import com.demien.sboot.domain.Category;
import com.demien.sboot.service.AbstractService;
import com.demien.sboot.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController@RequestMapping("/category")
public class CategoryController extends AbstractController<Category, Long> {

    private CategoryService service;

    @Autowired    public CategoryController(CategoryService service) {
        super(service);
        this.service=service;
    }

    @RequestMapping(value="findByName/{name}", method = RequestMethod.GET)
    public List<Category> findByName(@PathVariable String name) {
        return service.findByName(name);
    }
}


7.3 Controllers : ParamController

Just extending base controller class:
package com.demien.sboot.rest;

import com.demien.sboot.domain.Param;
import com.demien.sboot.service.ParamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController@RequestMapping("/param")
public class ParamController extends AbstractController<Param, Long> {

    @Autowired    public ParamController(ParamService service) {
        super(service);
    }
}


8. DB properties file  

DB connection properties are located in resources/application.properties file:

#DB properties:db.driver=org.h2.Driverdb.url=jdbc:h2:~/testdb.username=sadb.password=sa
#Hibernate Configuration:db.hibernate.dialect=org.hibernate.dialect.H2Dialectdb.hibernate.show_sql=falsedb.entitymanager.packages.to.scan=com.demien.sboot.domaindb.hibernate.hbm2ddl.auto = create

9. DB Config class

Now we can create Spring Configuration file which will be based on  resources/application.properties file :

package com.demien.sboot;

import org.hibernate.ejb.HibernatePersistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Properties;

@Configuration@EnableTransactionManagement@ComponentScan("com.demien.sdata")
@PropertySource("classpath:application.properties")
@EnableJpaRepositories("com.demien.sboot.repository")
public class DBConfig {


private static final String PROP_DATABASE_DRIVER = "db.driver";
private static final String PROP_DATABASE_PASSWORD = "db.password";
private static final String PROP_DATABASE_URL = "db.url";
private static final String PROP_DATABASE_USERNAME = "db.username";
private static final String PROP_HIBERNATE_DIALECT = "db.hibernate.dialect";
private static final String PROP_HIBERNATE_SHOW_SQL = "db.hibernate.show_sql";
private static final String PROP_ENTITYMANAGER_PACKAGES_TO_SCAN = "db.entitymanager.packages.to.scan";
private static final String PROP_HIBERNATE_HBM2DDL_AUTO = "db.hibernate.hbm2ddl.auto";

@Resourceprivate Environment env;

    @Bean    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(env.getRequiredProperty(PROP_DATABASE_DRIVER));
        dataSource.setUrl(env.getRequiredProperty(PROP_DATABASE_URL));
        dataSource.setUsername(env.getRequiredProperty(PROP_DATABASE_USERNAME));
        dataSource.setPassword(env.getRequiredProperty(PROP_DATABASE_PASSWORD));

        return dataSource;
    }

    @Bean    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
        entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROP_ENTITYMANAGER_PACKAGES_TO_SCAN));

        entityManagerFactoryBean.setJpaProperties(getHibernateProperties());

        return entityManagerFactoryBean;
    }

    @Bean    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());

        return transactionManager;
    }

    private Properties getHibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", env.getRequiredProperty(PROP_HIBERNATE_DIALECT));
        properties.put("hibernate.show_sql", env.getRequiredProperty(PROP_HIBERNATE_SHOW_SQL));
        properties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty(PROP_HIBERNATE_HBM2DDL_AUTO));

        return properties;
    }
}



10. Application config class

We're almost done! All we need now is to start our application using main class :
package com.demien.sboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplicationpublic class App {

    public static void main(String[] args) {
        SpringApplication.run(new Class<?>[]{App.class, DBConfig.class}, args);
    }
}

Because of using SpringBoot we don't need Tomcat/Jetty - we just have to run main class. Just after starting we can open browser  and point into test services. For ParamContoller we can open:
http://localhost:8080/param/hello

Browser will show : 
Hello from controller:class com.demien.sboot.rest.ParamController

11. Testing  

We are done with the deveopment, but how can webe sure that our application is working as expected ? Of cource, we have to test it !

11.1 Testing : ObjectPopulator

First of all, let's crate ObjectPopulator for filling empty objects with values.  

package com.demien.sboot.util;

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

public class ObjectPopulator {

    public static Object populate(final Object instance)
            throws Exception {

        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(Long.class)) {
                eachField.set(instance, getRandomLong());
            } 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 {
                   //throw new RuntimeException("Field "+ eachField.getName()+" was not populated!");                }
            }

        return instance;

    }

    private static Integer getRandomInteger() {
        return new Integer((int) (Math.random() * 1000));
    }
    private static Long getRandomLong() {
        return new Long((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;
    }
}


11.2 Testing : AbstractIT

As always, I created base abstract class for testing common methods. For that I have 4 methods annotated with @Test. These methods will be inherited by child classes and they will use them for testing THEIR methods of rest controller.

package com.demien.sboot.it;

import com.demien.sboot.App;
import com.demien.sboot.DBConfig;
import com.demien.sboot.domain.IPersistable;
import com.demien.sboot.rest.AbstractController;
import com.demien.sboot.util.ObjectPopulator;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;


import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {App.class, DBConfig.class})
@WebAppConfigurationpublic abstract class AbstractIT<T extends IPersistable<ID>, ID extends Serializable> {

    private AbstractController<T,ID> controller;
    private JpaRepository<T,ID> repository;
    private String urlPrefix;
    private Class<T> cl;

    protected MockMvc mockMvc;

    private HttpMessageConverter jsonConverter;

    @Autowired    private WebApplicationContext webApplicationContext;

    private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
            MediaType.APPLICATION_JSON.getSubtype(),
            Charset.forName("utf8"));


    @Autowired    void setConverters(HttpMessageConverter<?>[] converters) {

        this.jsonConverter = Arrays.asList(converters).stream().filter(
                hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get();

        Assert.assertNotNull("the JSON message converter must not be null",
                this.jsonConverter);
    }

    public void setController(AbstractController<T,ID> controller) {
        this.controller=controller;
    }


    public void setRepository(JpaRepository<T,ID> repository) {
        this.repository=repository;
    }


    public AbstractIT(String urlPrefix, Class<T> cl) {
        this.urlPrefix=urlPrefix;
        this.cl=cl;
    }


    protected String jsonToObject(Object o) throws IOException {
        MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
        this.jsonConverter.write(o, MediaType.APPLICATION_JSON, mockHttpOutputMessage);
        return mockHttpOutputMessage.getBodyAsString();
    }

    protected Object jsonToObject(String json) throws IOException {
        Object result = null;
        if (json!=null && json.substring(0,1).equals("[")) {
            result=new ArrayList<T>();
            String jsonTrimmed=json.substring(2,json.length()-2);
            String[] valArray=jsonTrimmed.split("\\},\\{");
            for (String val:valArray) {
                T element=(T)jsonToObject("{"+val+"}");
                ((List)result).add(element);
            }


        } else {
            MockHttpInputMessage mockHttpInputMessage = new MockHttpInputMessage(json.getBytes());

            try {
                result = this.jsonConverter.read(cl, mockHttpInputMessage);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (result == null) {
                mockHttpInputMessage = new MockHttpInputMessage(json.getBytes());
                result = this.jsonConverter.read(ArrayList.class, mockHttpInputMessage);
            }
        }
        return result;
    }

    protected T getTestEntity() {
        T result= null;
        try {
            result = cl.newInstance();
            ObjectPopulator.populate(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Before    public void init() {
        this.mockMvc = webAppContextSetup(webApplicationContext).build();
    }

    public String templateGetRequest(String url) throws Exception {
        String result=mockMvc.perform(get(url)
                        .contentType(contentType))
                        .andExpect(status().isOk())
                        .andReturn().getResponse().getContentAsString()
                ;
        return result;
    }

    public String templatePostRequest(String url, String json) throws Exception {
        String result=mockMvc.perform(post(url)
                .content(json)
                .contentType(contentType))
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString()
                ;
        return result;
    }

    public T callSave(T entity) throws Exception {
        String json= templatePostRequest("/" + urlPrefix + "/save/", jsonToObject(entity));
        return (T)jsonToObject(json);
    }

    public T callFindOne(ID entityId) throws Exception {
        String json= templateGetRequest("/" + urlPrefix + "/findOne/" + entityId.toString());
        return (T)jsonToObject(json);
    }

    public List<T> callFindAll() throws Exception {
        String json= templateGetRequest("/" + urlPrefix + "/findAll/");
        return (List<T>)jsonToObject(json);
    }

    public void callDelete(T entity) throws Exception {
        templatePostRequest("/" + urlPrefix + "/delete/", jsonToObject(entity));
    }

    @Test    public void saveTestForNewElement() throws Exception {
        T testEntity=getTestEntity();

        int countBefore=repository.findAll().size();
        testEntity= callSave(testEntity);
        int countAfter=repository.findAll().size();

        Assert.assertEquals("count AFTER ADD should be count BEFORE +1 ", countAfter, countBefore+1);
        Assert.assertTrue("test element should be present in repository", repository.findAll().contains(testEntity));

    }

    @Test    public void saveTestForExistingElement() throws Exception {
        T testEntity=getTestEntity();
        testEntity=repository.save(testEntity);


        T updatedEntity=getTestEntity();
        updatedEntity.setId(testEntity.getId());

        updatedEntity=callSave(updatedEntity);


        Assert.assertTrue("old element should not be present in repository", !repository.findAll().contains(testEntity));
        Assert.assertTrue("updated element should be present in repository", repository.findAll().contains(updatedEntity));

    }

    @Test    public void findAllTest() throws Exception {
        T testEntity=getTestEntity();
        testEntity=repository.save(testEntity);
        List<T> all= callFindAll();
        Assert.assertTrue("test element should be present in result list", all.contains(testEntity));

    }


    @Test    public void getByIdTest() throws Exception {
        T testEntity=getTestEntity();
        testEntity=repository.save(testEntity);
        ID id=testEntity.getId();

        T returnEntity=callFindOne(id);
        Assert.assertTrue("return element should be the same as original element", testEntity.equals(returnEntity));
    }

    @Test    public void deleteTest() throws Exception {
        T testEntity=getTestEntity();
        testEntity=repository.save(testEntity);
        List<T> all= repository.findAll();
        Assert.assertTrue("repository should contain SAVED element", all.contains(testEntity));
        callDelete(testEntity);
        all= repository.findAll();
        Assert.assertTrue("repository should NOT contain DELETED element", !all.contains(testEntity));

    }

}


11.3 Testing : CategoryIT

Testing of all base methods like category.save, category.findById, category.findAll and category.delete will be inherited, so we just need to implement only one method  - findByName:
package com.demien.sboot.it;

import com.demien.sboot.domain.Category;
import com.demien.sboot.repository.CategoryRepository;
import com.demien.sboot.rest.CategoryController;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public class CategoryIT extends AbstractIT<Category, Long> {

    private CategoryController controller;
    private CategoryRepository repository;
    private final static String CATEGORY_URL_PREFIX ="category";

    @Autowired    public void setCategoryController(CategoryController controller) {
        this.controller=controller;
        super.setController(controller);
    }

    @Autowired    public void setCategoryRepository(CategoryRepository repository) {
        this.repository=repository;
        super.setRepository(repository);
    }

    public CategoryIT() {
        super(CATEGORY_URL_PREFIX, Category.class);
    }

    @Test    public void findByNameTest() throws Exception {
        Category entity=getTestEntity();
        entity.setCategoryName("Hello world!");
        repository.save(entity);

        String json= templateGetRequest("/" + CATEGORY_URL_PREFIX + "/findByName/world");
        List<Category> returned=(List<Category>)jsonToObject(json);
        Assert.assertTrue("", returned.get(0).getCategoryName().equals("Hello world!"));

    }

}

11.4 Testing : ParamIT

For testing ParamController - we just need to extend from base class :
package com.demien.sboot.it;


import com.demien.sboot.domain.Param;
import com.demien.sboot.repository.ParamRepository;
import com.demien.sboot.rest.ParamController;
import org.springframework.beans.factory.annotation.Autowired;

public class ParamIT extends AbstractIT<Param, Long> {

    private ParamController controller;
    private ParamRepository repository;

    public ParamIT() {
        super("param", Param.class);
    }

    @Autowired    public void setController(ParamController controller) {
        this.controller = controller;
        super.setController(controller);
    }

    @Autowired    public void setRepository(ParamRepository repository) {
        this.repository = repository;
        super.setRepository(repository);
    }
}


12. The End


As a result we have application  with "db part" provided by SprindData + and "rest part" provided by SpringBoot. Full source code can be downloaded from here.

p.s.
related posts :

 
Restfull services in Java using Apache CXF with integration testing using embedded JETTY

1 comment: