Saturday, October 3, 2015

Spring boot - simple example

1. Intro

Of course, it's very easy to create rest-web application in java : there are a lot of frameworks and libraries for that, for example Spring MVC, Apache CXF, etc.
But how to test created application ? First of all, you have to build .war file. After that - deploy it to Tomcat. Ok, now application is running, but again : how to test rest services ?  I, for example created for that tool which is using apache.http.client to call rest services. Also I created tool for starting(and testing) application using embedded-jetty, without deploing it to tomcat. And I think, a lot of developers have have such tools.

Spring boot - created to make starting of application and it testing very east out-of-the-box: http://projects.spring.io/spring-boot/


2. Project structure

Project can be generated using online constructor : http://start.spring.io/
 
I created simple project with just one rest-controller, one repository for emulating working with DB, one domain object and one test class. Project was generated by online constructor, I just added new files, renamed main "run" file to App and removed test file.



3. pom.xml

It also was generated by constructor, I just added one more dependency (com.jayway.jsonpath).

<?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>demo</name>
   <description>Demo project for Spring Boot</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.2.6.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-web</artifactId>
      </dependency>
      
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>

      <dependency>
         <groupId>com.jayway.jsonpath</groupId>
         <artifactId>json-path</artifactId>
         <scope>test</scope>
      </dependency>

   </dependencies>
   
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
   

</project>



4. Entity

Very simple entity class with just 2 fields.
package com.demien.sboot;

public class TestEntity {
    private Integer entityId;
    private String entityName;

    public TestEntity() {}

    public Integer getEntityId() {
        return entityId;
    }

    public void setEntityId(Integer entityId) {
        this.entityId = entityId;
    }

    public String getEntityName() {
        return entityName;
    }

    public void setEntityName(String entityName) {
        this.entityName = entityName;
    }
}

5. Repository 

- simulation of DB repository. I used static ArrayList for storing elements of TestEntity type. And only 3 operations was implemented : add, getById and getAll.

package com.demien.sboot;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;


@Componentpublic class TestRepository {
    private static List<TestEntity> storage=new ArrayList<TestEntity>();


    public TestEntity add(TestEntity entity) {
        entity.setEntityId(storage.size()+1);
        storage.add(entity);
        return entity;
    }

    public TestEntity getById(Long entityId) {
        return storage.get(  entityId.intValue()-1);
    }

    public int getMaxId(){
        return storage.size();
    }

    public List<TestEntity> getAll() {
        return storage;
    }



}

6. Controller

Creation of rest-controller is not very hard : just few annotations and calls of repository's methods. 

package com.demien.sboot;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController@RequestMapping("/entity")
public class TestController {

    @Autowired    TestRepository repository;

    @RequestMapping("/hello")
    public String sayHello() {
        return "Hello, world!";
    }

    @RequestMapping(value = "add",  method = RequestMethod.POST)
    public @ResponseBody    TestEntity add(@RequestBody TestEntity entity) {
        TestEntity result=repository.add(entity);
        return result;
    }

    @RequestMapping(value="get/{entityId}", method = RequestMethod.GET)
    public TestEntity get(@PathVariable Long entityId) {
        return repository.getById(entityId);
    }

    @RequestMapping(value="getall", method = RequestMethod.GET)
    public List<TestEntity> getAll() {
        return repository.getAll();
    }

}


7. Application main(starter) file

This file was generated by constructor, I just renamed it. Just after creation of controller we can run App file and it will run our application on embedded-tomcat. And we can test our application by opening in browser URL: http://localhost:8080/entity/hello - it should return "Hello, world!"

As you can see - running of application is VERY easy. It even can be packed into jar file and running using java -jar ...... without tomcat!

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(App.class, args);
    }
}

8. Test of our controller

Test class class of our controller, from the first look, seems to be complicated - that it just because it has some logic related with json-transformations and some init lines of code for mockMvc. In real life all stuff like this, can be placed in parent test-base-class.
   Logic related with testing itself(methods with @Test annotation) - is very simple and clear.

package com.demien.sboot;

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.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
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.nio.charset.Charset;
import java.util.Arrays;

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;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.hasSize;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = App.class)
@WebAppConfigurationpublic class TestControllerTest {

    private MockMvc mockMvc;

    @Autowired    TestRepository repository;


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

    private HttpMessageConverter mappingJackson2HttpMessageConverter;

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

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

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

    @Autowired    private WebApplicationContext webApplicationContext;

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

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

    @Test    public void helloTest() throws Exception {
        mockMvc.perform(get("/entity/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("Hello, world!"));
    }

    @Test    public void addTest() throws Exception {
        TestEntity entity=new TestEntity();
        entity.setEntityName("Test");

        mockMvc.perform(post("/entity/add/")
                .content(this.json(entity))
                .contentType(contentType))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.entityId", is(repository.getMaxId())))
                .andExpect(jsonPath("$.entityName", is(entity.getEntityName())))
        ;
    }

    @Test    public void getTest() throws Exception {
        TestEntity entity=new TestEntity();
        entity.setEntityName("TestGet");
        entity=repository.add(entity);

        mockMvc.perform(get("/entity/get/" + entity.getEntityId()))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.entityId", is(entity.getEntityId())))
                .andExpect(jsonPath("$.entityName", is(entity.getEntityName())))
        ;

    }

    @Test    public void getAllTest() throws Exception {
        TestEntity entity=new TestEntity();
        entity.setEntityName("TestGetAll");
        entity=repository.add(entity);

        mockMvc.perform(get("/entity/getall"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(repository.getAll().size())))
        ;

    }
}
 

9. The end

Spring boot helped us to create rest application with can be run very easy. And also this application is very easy for testing. Full source code can be downloaded from here.

No comments:

Post a Comment