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()))) ; } }