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