Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

Saturday, January 5, 2019

Spring MVC: unit and integration tests

1.Intro 

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

2. Project structure

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





3. build.gradle

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

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

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


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

4. Main application starter

Nothing interesting is here. Just SpringApplication.run

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

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

}



5. Domain objects

Thanks to Lombok, my domain objects are really tiny!

 package com.demien.sprmvc.domain;

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


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

6. Rest controller

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



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

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

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

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

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

}


7. Unit test

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

@WebMvcTest(UserController.class)

- our mockMvc will be created for UserController class. 


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

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

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

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

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

}


Results:



8. Integration test

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

and  test needed rest enpoints using TestRestTemplate.

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

public class SprmvcApplicationIT {

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

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

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

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

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

}


Results:




9. The end 

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

Tuesday, December 11, 2018

Spring: how can I test my bean?

0. Intro

Testing is very important part of development. In this post I want to show most popular options of testing Spring application.

1. Test application

I want to create the simulation of permission checking logic: main idea is to check if the user has access to group. This operation will be performed by PermissionService. But it also has dependency to service UserGroupService which can return list of user groups .

1.1. Project structure

I have two domain classes: User and Group, two interfaces/services: Permission and UserGroup, config class and main runner: App class.



1.2. Gradle build

I'm using lombok to reduce some boilerplate code, so lombok plugin is being applied. Also, and beside spring  dependencies I have mockito for testing.


plugins {
    id 'java'    id 'io.franzbecker.gradle-lombok' version '1.8'    id 'application'}

group 'com.demien'version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.springframework', name: 'spring-core', version: '5.1.3.RELEASE'    compile group: 'org.springframework', name: 'spring-context', version: '5.1.3.RELEASE'    compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.25'

    testCompile group: 'junit', name: 'junit', version: '4.12'    testCompile group: 'org.springframework', name: 'spring-test', version: '5.1.3.RELEASE'    testCompile group: 'org.mockito', name: 'mockito-all', version: '1.10.19'}

mainClassName = 'com.demien.springtest.App'

1.3. Services

For services I'm creating interface and implementation.

UserGroupService should return for provided user list of assigned  groups:

package com.demien.springtest.service;
import com.demien.springtest.domain.Group;import com.demien.springtest.domain.User;import java.util.List;
public interface UserGroupService {

    List<Group> getUserGroups(User user);
}


Of course in real life it should call DB or another service to get this list of groups, but for this example I'm just returning two "hardcoded" groups: "first" and "second". 

package com.demien.springtest.service;
import com.demien.springtest.domain.Group;import com.demien.springtest.domain.User;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Repository;
import java.util.Arrays;import java.util.List;
@Slf4j@Repository@Qualifier("UserGroupService-main")
public class UserGroupServiceImpl implements UserGroupService {

    @Override    public List<Group> getUserGroups(User user) {
      log.info("It should be DB call here");        return Arrays.asList(
                new Group("first"),                new Group("second")
        );    }
}


Permission service should check if provided user has access to provided group.

package com.demien.springtest.service;
import com.demien.springtest.domain.User;
public interface PermissionService {
    boolean hasAccess(User user, String groupName);}

For this, service is calling UserGroupService to get list of groups assigned to user and checks if provided group exists in this list.

package com.demien.springtest.service;
import com.demien.springtest.domain.User;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.stereotype.Service;
@Slf4j@Service@Qualifier("PermissionService-main")
public class PermissionServiceImpl implements PermissionService {

    @Autowired    @Qualifier("UserGroupService-main")
    private UserGroupService userGroupService;
    @Override    public boolean hasAccess(User user, String groupName) {
        log.info("calling userGroupService");        return userGroupService.getUserGroups(user).stream().anyMatch(group -> group.getName().equals(groupName));    }
}

1.4 Other stuff. 

Domain objects are very simple:
Group:
package com.demien.springtest.domain;
import lombok.AllArgsConstructor;import lombok.Getter;
@Getter@AllArgsConstructorpublic class Group {

    String name;}


User:
package com.demien.springtest.domain;
public class User {
}


Config class is just specifying package to scan for spring annotations:
package com.demien.springtest.config;
import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;
@Configuration@ComponentScan(basePackages =  {"com.demien.springtest.service"})
public class MainConfig {

}

Main class: little bit more interesting: we have to start spring context get our PermissionService and check if user has access to group:

package com.demien.springtest;
import com.demien.springtest.config.MainConfig;import com.demien.springtest.domain.User;import com.demien.springtest.service.PermissionService;import lombok.extern.slf4j.Slf4j;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@Slf4jpublic class App {
    public static void main(String[] args) {
        final User dummyUser = new User();        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);        PermissionService permissionService =  context.getBean(PermissionService.class);        log.info("Permission result: for [admin] group: " +  permissionService.hasAccess(dummyUser, "admin"));        log.info("Permission result: for [first] group: " +  permissionService.hasAccess(dummyUser, "first"));    }
}

1.4. Execution

As expected, PermissionService calls userGroupService, which actually has to call some DB, and our user has access to group "first" and don't have access to group "admin" (we hardcoded "first" and "second" in userGroupService)

[main] INFO com.demien.springtest.service.PermissionServiceImpl - calling userGroupService
[main] INFO com.demien.springtest.service.UserGroupServiceImpl - It should be DB call here
[main] INFO com.demien.springtest.App - Permission result: for [admin] group: false

[main] INFO com.demien.springtest.service.PermissionServiceImpl - calling userGroupService
[main] INFO com.demien.springtest.service.UserGroupServiceImpl - It should be DB call here
[main] INFO com.demien.springtest.App - Permission result: for [first] group: true



2. Testing 

Application seems to be working, but how can we create some unit tests for it?

2.1. Spring test

Most straightforward approach here  - just run our main spring context, execute hasAccess mechod and check the results:


package com.demien.springtest;
import com.demien.springtest.config.MainConfig;import com.demien.springtest.domain.User;import com.demien.springtest.service.PermissionService;import org.junit.Test;import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = MainConfig.class)
public class PermissionServiceSpringTest {

    final User dummyUser = new User();
    @Autowired    @Qualifier("PermissionService-main")
    private PermissionService permissionService;

    @Test    public void calculateSum() {
        assertTrue(permissionService.hasAccess(dummyUser, "first"));        assertFalse(permissionService.hasAccess(dummyUser, "zero"));    }
}

Looks good, but the problem here: we have to start our main spring context, what if it has 100 beans? And anyway, we just want to test PermissionService but it's calling UserGroupService inside - so it's not a unit test, but integration test!

2.2 Spring stub test

Ok, we understand now that it's not a good idea to run the whole our main spring context, so let's create a test context with "stub" implementation of UserGroupService.

package com.demien.springtest;
import com.demien.springtest.domain.Group;import com.demien.springtest.domain.User;import com.demien.springtest.service.PermissionService;import com.demien.springtest.service.PermissionServiceImpl;import com.demien.springtest.service.UserGroupService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Arrays;import java.util.List;
import static org.junit.Assert.assertFalse;import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = PermissionServiceStubTest.TestConfig.class)
public class PermissionServiceStubTest {

    @Configuration    static class TestConfig {

        @Bean        @Qualifier("UserGroupService-main")
        public UserGroupService testUserGroupService() {
            return new UserGroupServiceTestImpl();        }

        @Bean        @Qualifier("PermissionService-test")
        public PermissionService testPermissionService() {
            return new PermissionServiceImpl();        }

    }

    static class UserGroupServiceTestImpl implements UserGroupService {

        @Override        public List<Group> getUserGroups(User user) {
            return Arrays.asList(
                    new Group("stubGroup1"), new Group("stubGroup2")
            );        }
    }

    final User dummyUser = new User();
    @Autowired    @Qualifier("PermissionService-test")
    private PermissionService permissionService;

    @Test    public void calculateSum() {
        assertTrue(permissionService.hasAccess(dummyUser, "stubGroup2"));        assertFalse(permissionService.hasAccess(dummyUser, "stubGroup5"));    }

}

It's much better now! We're not running our main context - just test context with needed beans. And real beans can be replaced with "stub" (UserGroupServiceTestImpl) versions!

2.3 Mock test

The problem with previous approach is: looks like for every bean, we will have to create an additional test context, and create a stub implementation of every dependency. Can we make it more simple? Sure we can: using mocks!

package com.demien.springtest;
import com.demien.springtest.domain.Group;import com.demien.springtest.domain.User;import com.demien.springtest.service.PermissionService;import com.demien.springtest.service.PermissionServiceImpl;import com.demien.springtest.service.UserGroupService;import org.junit.Assert;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.mockito.BDDMockito;import org.mockito.InjectMocks;import org.mockito.Matchers;import org.mockito.Mock;import org.mockito.runners.MockitoJUnitRunner;
import java.util.Arrays;
@RunWith(MockitoJUnitRunner.class)
public class PermissionServiceMockTest {

    @Mock    private UserGroupService userGroupService;
    @InjectMocks    private PermissionService permissionService = new PermissionServiceImpl();
    final User dummyUser = new User();
    @Before    public void init() {
        BDDMockito.given(userGroupService.getUserGroups(Matchers.any(User.class))).willReturn(
                Arrays.asList(new Group("mockGroup1"), new Group("mockGroup2"), new Group("mockGroup3") )
        );    }

    @Test    public void test() {
        Assert.assertTrue(permissionService.hasAccess(dummyUser, "mockGroup2"));        Assert.assertFalse(permissionService.hasAccess(dummyUser, "newGroup"));    }

}


Now don't have to start spring context at all! We just have to define behavior of our mock of UserGroupService and run the test! Excellent ! But can it be better?

2.4. Refactoring

Let's take a closer look now. Actually, the main functionality we want to test is here:
userGroupService.getUserGroups(user).stream().anyMatch(group -> group.getName().equals(groupName));

Let's now try to split it into 2 functions:

public boolean contains(List<Group> groups, String groupName) {
    return groups.stream().anyMatch(group -> group.getName().equals(groupName));}

@Overridepublic boolean hasAccess(User user, String groupName) {
    log.info("calling userGroupService");    return contains(userGroupService.getUserGroups(user), groupName);}


Now our business logic isolated from userGroupService in method "contains" . And we can easily test with method using just JUnit without any mocks/stubs/spring contexts!

@Testpublic void containsTest() {
    PermissionServiceImplRefactored refactored = new PermissionServiceImplRefactored();    List<Group> testGroups = Arrays.asList(
            new Group("first"),            new Group("second")
    );    Assert.assertTrue(refactored.contains(testGroups, "first"));    Assert.assertFalse(refactored.contains(testGroups, "trash"));}


2.5. The end.

As always, simplest way is the best way! And remember, if you're starting spring context - it's not a unit test!  Full source code can be downloaded from here

Monday, May 9, 2016

SPARK - rest framework for java


Don't be surprised : ApacheSpark and SparkJava - it's a 2 different technologies !
In this post a'm talking about SparkJava - simple rest framework for Java :  http://sparkjava.com/

Related posts :
Spring Boot - simple example
SpringBoot with SpringData and H2 database

SpringBoot is a very good framework, but it takes as dependencies almost full stack of all Spring libraries. And if you are not planing to use them in your project - you will start thinking about more "compact" rest frameworks. SparkJava is one of them.

1. Goal 

As a lazy developer I want to create a rest application with ability to add new entities in very simple way : by just extending "base" class :
GenericController<Item> itemController=new GenericController<Item>("/item", Item.class, itemService);



2. Application structure

Standard "mave-based" application structure:


3.Maven project(pom.xml) file

Just spark,slfj,gson and junit dependencies:

<?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>sparktest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
    <dependency>
        <groupId>com.sparkjava</groupId>
        <artifactId>spark-core</artifactId>
        <version>2.0.0</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.7</version>
    </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

    </dependencies>


</project>


4. Domain objects

For my POJO data objects i created interface  IPersistable
public interface IPersistable {
    Long getId();
    void setId(Long id);

}

For having ability to operate ID field(by getId method) in controllers and test classes. So, all my domain objects are implementing this interface :

public class Item implements IPersistable {

    private Long id;
    private String name;
    private Long parentId;

public class Param implements IPersistable {

    private Long id;
    private String name;
    private String dataType;
    private Item item;

5. Controller

As I mentioned before, I'm too lazy, so I want to move common operations like "add", "get", "update", "delete" into one class :

package com.demien.sparktest.controller;

import com.demien.sparktest.util.JsonUtil;
import com.demien.sparktest.domain.IPersistable;
import com.demien.sparktest.service.GenericService;
import spark.Request;
import spark.Response;
import spark.Spark;

public class GenericController<T extends IPersistable> {
    private GenericService<T> service;
    private Class<T> cl;

    public GenericController(String basePath, Class<T> cl, GenericService<T> service) {
        this.cl=cl;
        this.service=service;
        Spark.get(basePath,this::getAll, JsonUtil::toJson);
        Spark.get(basePath+"/:id",this::getById, JsonUtil::toJson);
        Spark.get(basePath+"/test",this::test, JsonUtil::toJson);
        Spark.post(basePath,this::add, JsonUtil::toJson);
        Spark.put(basePath,this::update, JsonUtil::toJson);
        Spark.delete(basePath,this::delete, JsonUtil::toJson);
    }

    public Object test(Request request, Response response) {
        return "Hello world!";
    }

    public Object getAll(Request request, Response response) {
        return service.getAll();
    }

    public Object getById(Request request, Response response) {
        String id = request.params(":id");
        return service.getById(Long.parseLong(id));
    }

    public T restoreObjectFromRequest(Request request) {
        return (T)JsonUtil.toObject(request.body(),cl);
    }

    public Object add(Request request, Response response) {
        return service.add(restoreObjectFromRequest(request));
    }

    public Object update(Request request, Response response) {
        return service.update(restoreObjectFromRequest(request));
    }

    public Object delete(Request request, Response response) {
        service.delete(restoreObjectFromRequest(request));
        return "";
     }




}

So, now, for my entities(item and param) I have just to extend this class, without creation of controllers  :
GenericController<Item> itemController=new GenericController<Item>(ITEM_PATH, Item.class, itemService);
GenericController<Param> paramController=new GenericController<Param>(PARAM_PATH, Param.class, paramService);


6. "Dummy" service. 

It's just a simple demo project, so I decided not to use Hibernate, and created simple class for storing objects in a HashMap :

package com.demien.sparktest.service;

import com.demien.sparktest.domain.IPersistable;

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

public class GenericService<T extends IPersistable> {
    private Long maxId=0L;

    private Map<Long, T> storage=new HashMap<Long, T>();

    public void clearStorage() {
        storage.clear();
    }

    public T getById(Long id) {
        return storage.get(id);
    }

    public T add(T element) {
        Long id=element.getId();
        if (id==null) {
            maxId++;
            element.setId(maxId);
        } else {
            if (maxId.longValue()<id.longValue()) {
                maxId=id+1;
            }
        }
        return update(element);
    }

    public List<T> getAll() {
        List<T> result=new ArrayList<T>();
        for (T element:storage.values()) {
            result.add(element);
        }
        return result;
    }

    public T update(T element) {
        storage.put(element.getId(), element);
        return storage.get(element.getId());
    }

    public void delete(T element) {
        storage.remove(element.getId());
    }

}

7. Main application file. 

Spark is running just like regular java application. In main() procedure i have to "start" my controllers :
package com.demien.sparktest;

import com.demien.sparktest.controller.GenericController;
import com.demien.sparktest.domain.Param;
import com.demien.sparktest.service.GenericService;
import spark.Spark;
import com.demien.sparktest.domain.Item;

public class App {

    public final static int SPARK_PORT=8080;
    public final static String APP_PATH="http://localhost:"+SPARK_PORT;

    public final static GenericService<Item> itemService=new GenericService<>();
    public final static String ITEM_PATH="/item";

    public final static GenericService<Param> paramService=new GenericService<>();
    public final static String PARAM_PATH="/param";

    public static void main(String[] args) {
        Spark.setPort(8080);
        GenericController<Item> itemController=new GenericController<Item>(ITEM_PATH, Item.class, itemService);
        GenericController<Param> paramController=new GenericController<Param>(PARAM_PATH, Param.class, paramService);
    }

}

8. Utils

Also I had to create few simple utils :


8.1. JsonUtil  - just for conversion json<=>object

package com.demien.sparktest.util;

import com.google.gson.Gson;

public class JsonUtil {
    public static String toJson(Object object) {
        return new Gson().toJson(object);
    }

    public static Object toObject(String json, Class<?> cl) {
        return new Gson().fromJson(json, cl);
    }
}


8.2. RestTestUtil  - for testing : sending requests 

package com.demien.sparktest.util;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class RestTestUtil {

    public static class RequestResult {

        public final String body;
        public final int status;

        private RequestResult(int status, String body) {
            this.body = body;
            this.status = status;
        }
    }

    public static RequestResult sendRequest(String method, String path, String urlParameters) throws IOException {

        URL url = new URL(path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setDoOutput(true);
        conn.setInstanceFollowRedirects(false);
        conn.setRequestMethod(method);

        if (urlParameters!=null) {
            byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
            int postDataLength = postData.length;

            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("charset", "utf-8");
            conn.setRequestProperty("Content-Length", Integer.toString(postDataLength));
            conn.setUseCaches(false);
            conn.getOutputStream().write(postData);
        }

        Reader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (int c; (c = in.read()) >= 0; )
            sb.append((char) c);
        String responseBody = sb.toString();
        int responseCode=conn.getResponseCode();
        return new RequestResult(responseCode, responseBody);

    }

    public static RequestResult sendRequest(String method, String path) throws IOException {
        return sendRequest(method, path, null);
    }


}

8.3 object populator - for testing : to "fill" test object with random generated data. 

package com.demien.sparktest.util;

import com.demien.sparktest.domain.IPersistable;
import com.demien.sparktest.domain.Item;

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

public class ObjectPopulator {

    interface RandomGenerator {
        Object getRandomValue();
    }

    enum DataType {
        Integer(() -> {
            return new Integer((int) (Math.random() * 1000));
        }),
        Long(() -> {
            return new Long((long) (Math.random() * 1000));
        }),
        Date(()-> {
            return new Date(new Date().getTime() - (int) (Math.random() * 1000 * 60 * 60 * 24 * 100));
        }),
        String(() -> {
            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 RandomGenerator generator;

        DataType(RandomGenerator generator) {
            this.generator = generator;
        }

        Object getRandomValue() {
            return generator.getRandomValue();
        }
    }

    public static Object populate(IPersistable instance) throws IllegalAccessException {
        List<Field> fields = getAllFields(instance);
        for (Field eachField : fields) {
            eachField.setAccessible(true);
            String typeName=eachField.getType().getSimpleName();
            if (eachField.getType().getTypeName().startsWith("com.demien")) {
                Object obj=null;
                try {
                     obj=eachField.getType().newInstance();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }
                obj=populate((IPersistable) obj);
                eachField.set(instance, obj);
            } else {
                DataType dataType = DataType.valueOf(typeName);
                eachField.set(instance, dataType.getRandomValue());
            }
        }
        return instance;
    }


    private static List<Field> getAllFields(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;
    }

 
}

9. Generic integration test class

For base operations i created generic controller test class - other controllers will just extend it :
package com.demien.sparktest;

import com.demien.sparktest.domain.IPersistable;
import com.demien.sparktest.service.GenericService;
import com.demien.sparktest.util.JsonUtil;
import com.demien.sparktest.util.ObjectPopulator;
import com.demien.sparktest.util.RestTestUtil;
import org.junit.*;
import spark.Spark;

import java.util.List;

public abstract class GenericControllerIT<T extends IPersistable> {
    private String fullPath;
    private Class<T> cl;
    private GenericService<T> service;

    public GenericControllerIT(String basePath, Class<T> cl, GenericService<T> service){
        this.fullPath= App.APP_PATH+basePath;
        this.cl=cl;
        this.service=service;
    }

    @BeforeClass    public static void init() {
        App.main(null);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @AfterClass    public static void tearDown() {
        Spark.stop();
    }

    @Before    public void initTest() {
        service.clearStorage();
    }

    T getTestObject() throws Exception {
        T testObject=cl.newInstance();
        ObjectPopulator.populate(testObject);
        return testObject;
    }

    @Test    public void addTest() throws Exception {
        T testObject=getTestObject();
        RestTestUtil.RequestResult res= RestTestUtil.sendRequest("POST", fullPath, JsonUtil.toJson(testObject));
        Assert.assertEquals(200, res.status);
        Assert.assertEquals(service.getById(testObject.getId()), testObject);
    }

    @Test    public void getTest() throws Exception {
        T testObject=getTestObject();
        service.add(testObject);
        RestTestUtil.RequestResult res= RestTestUtil.sendRequest("GET", fullPath +"/"+testObject.getId());
        Assert.assertEquals(200, res.status);
        T receivedObject=(T)JsonUtil.toObject(res.body, cl);
        Assert.assertEquals(testObject, receivedObject);
    }

    @Test    public void getAllTest() throws Exception {
        T testObject1=getTestObject();
        service.add(testObject1);
        T testObject2=getTestObject();
        service.add(testObject2);
        RestTestUtil.RequestResult res= RestTestUtil.sendRequest("GET", fullPath);
        Assert.assertEquals(200, res.status);
        List<T> receivedObjectList=(List<T>)JsonUtil.toObject(res.body, java.util.List.class);
        Assert.assertTrue(receivedObjectList.size()==2);
    }


    @Test    public void updateTest() throws Exception {
        T testObject=getTestObject();
        service.add(testObject);
        T updatedObject=getTestObject();
        updatedObject.setId(testObject.getId());

        RestTestUtil.RequestResult res= RestTestUtil.sendRequest("PUT", fullPath, JsonUtil.toJson(updatedObject));
        Assert.assertEquals(200, res.status);

        T updatedObjectFromService=service.getById(testObject.getId());
        Assert.assertEquals(updatedObject, updatedObjectFromService);
    }

    @Test    public void deleteTest() throws Exception {
        T testObject=getTestObject();
        service.add(testObject);

        RestTestUtil.RequestResult res= RestTestUtil.sendRequest("DELETE", fullPath, JsonUtil.toJson(testObject));
        Assert.assertEquals(200, res.status);

        T deletedObject=service.getById(testObject.getId());
        Assert.assertNull(deletedObject);
    }





}


10. Integration test classes for ItemController and ParamController. 

Now we can just extend GenericControlerIT class and all tests for base operation will be inherited :

package com.demien.sparktest;

import com.demien.sparktest.domain.Item;

public class ItemControllerIT extends GenericControllerIT<Item> {
    public ItemControllerIT() {
        super(App.ITEM_PATH, Item.class, App.itemService);
    }
}


package com.demien.sparktest;

import com.demien.sparktest.domain.Param;

public class ParamControllerIT extends GenericControllerIT<Param> {
    public ParamControllerIT() {
        super(App.PARAM_PATH, Param.class, App.paramService);
    }
}

11. The end

Complete source code can be downloaded from here.