From 98826bb476604842e4bf7ef454450410a8d70c0c Mon Sep 17 00:00:00 2001 From: Matt Warman Date: Wed, 22 Apr 2015 17:12:32 -0400 Subject: [PATCH 1/6] Added the Springfox Swagger API documentation generator for web service APIs. --- pom.xml | 23 +++++- .../leanstacks/ws/SecurityConfiguration.java | 81 +++++++++++++++---- .../leanstacks/ws/SwaggerConfiguration.java | 43 ++++++++++ .../leanstacks/ws/web/SwaggerController.java | 16 ++++ .../resources/config/application.properties | 2 +- 5 files changed, 148 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/leanstacks/ws/SwaggerConfiguration.java create mode 100644 src/main/java/com/leanstacks/ws/web/SwaggerController.java diff --git a/pom.xml b/pom.xml index 56e0c6e..2ead8a4 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.leanstacks skeleton-ws-spring-boot - 1.0.1 + 1.2.0 Web Services Project Skeleton Skeleton project for RESTful web services using Spring Boot. @@ -19,7 +19,16 @@ UTF-8 1.7 18.0 + 2.0.0-SNAPSHOT + + + + jcenter-snapshots + jcenter + http://oss.jfrog.org/artifactory/oss-snapshot-local/ + + @@ -61,6 +70,18 @@ ${guava.version} + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + org.springframework.boot diff --git a/src/main/java/com/leanstacks/ws/SecurityConfiguration.java b/src/main/java/com/leanstacks/ws/SecurityConfiguration.java index 5a0ab69..01a0bf8 100644 --- a/src/main/java/com/leanstacks/ws/SecurityConfiguration.java +++ b/src/main/java/com/leanstacks/ws/SecurityConfiguration.java @@ -3,6 +3,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -22,7 +24,7 @@ */ @Configuration @EnableWebSecurity -public class SecurityConfiguration extends WebSecurityConfigurerAdapter { +public class SecurityConfiguration { @Autowired private AccountAuthenticationProvider accountAuthenticationProvider; @@ -55,21 +57,70 @@ public void configureGlobal(AuthenticationManagerBuilder auth) auth.authenticationProvider(accountAuthenticationProvider); } + + @Configuration + @Order(1) + public static class ApiWebSecurityConfigurerAdapter extends + WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http + .csrf().disable() + .antMatcher("/api/**") + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .httpBasic() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + } + + } + + @Configuration + @Order(2) + public static class ActuatorWebSecurityConfigurerAdapter extends + WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + + http + .csrf().disable() + .antMatcher("/actuators/**") + .authorizeRequests() + .anyRequest().hasRole("SYSADMIN") + .and() + .httpBasic() + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS); + + } + + } + + @Profile("docs") + @Configuration + public static class FormLoginWebSecurityConfigurerAdapter extends + WebSecurityConfigurerAdapter { - @Override - protected void configure(HttpSecurity http) throws Exception { - - http - .csrf().disable() - .authorizeRequests() - .antMatchers("/api/**").hasRole("USER") - .antMatchers("/actuators/**").hasRole("SYSADMIN") - .anyRequest().authenticated() - .and() - .httpBasic() - .and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); - + @Override + protected void configure(HttpSecurity http) throws Exception { + + http + .csrf().disable() + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin(); + + } + } } diff --git a/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java b/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java new file mode 100644 index 0000000..54cf15d --- /dev/null +++ b/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java @@ -0,0 +1,43 @@ +package com.leanstacks.ws; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import com.google.common.base.Predicate; + +@Profile("docs") +@Configuration +@EnableSwagger2 +public class SwaggerConfiguration { + + /** + * Create a Docket class to be used by Springfox's Swagger API Documentation + * framework. See http://springfox.github.io/springfox/ for more + * information. + * @return A Docket instance. + */ + @Bean + public Docket docket() { + Predicate paths = PathSelectors.ant("/api/**"); + + ApiInfo apiInfo = new ApiInfoBuilder() + .title("Project Skeleton for Spring Boot Web Services") + .description( + "The Spring Boot web services starter project provides a foundation to rapidly construct a RESTful web services application.") + .contact("LeanStacks.com").version("1.2.0").build(); + + Docket docket = new Docket(DocumentationType.SWAGGER_2) + .apiInfo(apiInfo).select().paths(paths).build(); + + return docket; + } + +} diff --git a/src/main/java/com/leanstacks/ws/web/SwaggerController.java b/src/main/java/com/leanstacks/ws/web/SwaggerController.java new file mode 100644 index 0000000..e636b62 --- /dev/null +++ b/src/main/java/com/leanstacks/ws/web/SwaggerController.java @@ -0,0 +1,16 @@ +package com.leanstacks.ws.web; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Profile("docs") +@Controller +public class SwaggerController { + + @RequestMapping("/docs") + public String getDocsPage() { + return "swagger-ui.html"; + } + +} diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index d12adc3..bc2973d 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -5,7 +5,7 @@ ## # Profile Configuration ## -spring.profiles.active=hsqldb,batch +spring.profiles.active=hsqldb,batch,docs ## # Web Server Configuration From b74ea590900fe9e696591b2417094afb42184c1b Mon Sep 17 00:00:00 2001 From: Matt Warman Date: Thu, 23 Apr 2015 06:52:37 -0400 Subject: [PATCH 2/6] Javadoc for Swagger changes. --- .../leanstacks/ws/SecurityConfiguration.java | 18 ++++++++++++++++++ .../leanstacks/ws/SwaggerConfiguration.java | 6 ++++++ .../leanstacks/ws/web/SwaggerController.java | 14 +++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/leanstacks/ws/SecurityConfiguration.java b/src/main/java/com/leanstacks/ws/SecurityConfiguration.java index 01a0bf8..7e11929 100644 --- a/src/main/java/com/leanstacks/ws/SecurityConfiguration.java +++ b/src/main/java/com/leanstacks/ws/SecurityConfiguration.java @@ -58,6 +58,12 @@ public void configureGlobal(AuthenticationManagerBuilder auth) } + /** + * This inner class configures the WebSecurityConfigurerAdapter instance for + * the web service API context paths. + * + * @author Matt Warman + */ @Configuration @Order(1) public static class ApiWebSecurityConfigurerAdapter extends @@ -81,6 +87,12 @@ protected void configure(HttpSecurity http) throws Exception { } + /** + * This inner class configures the WebSecurityConfigurerAdapter instance for + * the Spring Actuator web service context paths. + * + * @author Matt Warman + */ @Configuration @Order(2) public static class ActuatorWebSecurityConfigurerAdapter extends @@ -104,6 +116,12 @@ protected void configure(HttpSecurity http) throws Exception { } + /** + * This inner class configures the WebSecurityConfigurerAdapter instance for + * any remaining context paths not handled by other adapters. + * + * @author Matt Warman + */ @Profile("docs") @Configuration public static class FormLoginWebSecurityConfigurerAdapter extends diff --git a/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java b/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java index 54cf15d..472c55c 100644 --- a/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java +++ b/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java @@ -13,6 +13,12 @@ import com.google.common.base.Predicate; +/** + * The SwaggerConfiguration class provides configuration beans for the Swagger + * API documentation generator. + * + * @author Matt Warman + */ @Profile("docs") @Configuration @EnableSwagger2 diff --git a/src/main/java/com/leanstacks/ws/web/SwaggerController.java b/src/main/java/com/leanstacks/ws/web/SwaggerController.java index e636b62..f2ad6d9 100644 --- a/src/main/java/com/leanstacks/ws/web/SwaggerController.java +++ b/src/main/java/com/leanstacks/ws/web/SwaggerController.java @@ -4,12 +4,24 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +/** + * The SwaggerController is a Spring MVC web controller class which serves the + * Swagger user interface HTML page. + * + * @author Matt Warman + */ @Profile("docs") @Controller public class SwaggerController { + /** + * Request handler to serve the Swagger user interface HTML page configured + * to the mapped context path. + * + * @return A String name of the Swagger user interface HTML page name. + */ @RequestMapping("/docs") - public String getDocsPage() { + public String getSwaggerApiDocsPage() { return "swagger-ui.html"; } From 9341b837aca630f01787e09202bebd24bdf54eb1 Mon Sep 17 00:00:00 2001 From: Matt Warman Date: Mon, 27 Apr 2015 16:55:54 -0400 Subject: [PATCH 3/6] Renamed Swagger classes. Disabled docs profile by default. --- ...{SwaggerConfiguration.java => ApiDocsConfiguration.java} | 4 ++-- .../{SwaggerController.java => docs/ApiDocsController.java} | 6 +++--- src/main/resources/config/application.properties | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) rename src/main/java/com/leanstacks/ws/{SwaggerConfiguration.java => ApiDocsConfiguration.java} (93%) rename src/main/java/com/leanstacks/ws/web/{SwaggerController.java => docs/ApiDocsController.java} (82%) diff --git a/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java b/src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java similarity index 93% rename from src/main/java/com/leanstacks/ws/SwaggerConfiguration.java rename to src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java index 472c55c..cd6777a 100644 --- a/src/main/java/com/leanstacks/ws/SwaggerConfiguration.java +++ b/src/main/java/com/leanstacks/ws/ApiDocsConfiguration.java @@ -14,7 +14,7 @@ import com.google.common.base.Predicate; /** - * The SwaggerConfiguration class provides configuration beans for the Swagger + * The ApiDocsConfiguration class provides configuration beans for the Swagger * API documentation generator. * * @author Matt Warman @@ -22,7 +22,7 @@ @Profile("docs") @Configuration @EnableSwagger2 -public class SwaggerConfiguration { +public class ApiDocsConfiguration { /** * Create a Docket class to be used by Springfox's Swagger API Documentation diff --git a/src/main/java/com/leanstacks/ws/web/SwaggerController.java b/src/main/java/com/leanstacks/ws/web/docs/ApiDocsController.java similarity index 82% rename from src/main/java/com/leanstacks/ws/web/SwaggerController.java rename to src/main/java/com/leanstacks/ws/web/docs/ApiDocsController.java index f2ad6d9..9ffa8f0 100644 --- a/src/main/java/com/leanstacks/ws/web/SwaggerController.java +++ b/src/main/java/com/leanstacks/ws/web/docs/ApiDocsController.java @@ -1,18 +1,18 @@ -package com.leanstacks.ws.web; +package com.leanstacks.ws.web.docs; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** - * The SwaggerController is a Spring MVC web controller class which serves the + * The ApiDocsController is a Spring MVC web controller class which serves the * Swagger user interface HTML page. * * @author Matt Warman */ @Profile("docs") @Controller -public class SwaggerController { +public class ApiDocsController { /** * Request handler to serve the Swagger user interface HTML page configured diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index bc2973d..c6fcfaa 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -4,8 +4,9 @@ ## # Profile Configuration +# profiles: hsqldb, mysql, batch, docs ## -spring.profiles.active=hsqldb,batch,docs +spring.profiles.active=hsqldb,batch ## # Web Server Configuration From abea61e6ad9e4c6a28e0d21c0cc636cd4dd9f645 Mon Sep 17 00:00:00 2001 From: Matt Warman Date: Tue, 28 Apr 2015 06:15:15 -0400 Subject: [PATCH 4/6] Updated to Spring Boot v1.2.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2ead8a4..fc65eda 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 1.2.2.RELEASE + 1.2.3.RELEASE From 2c3b309abdadfe0eb471b65cb6e8e0831955d88c Mon Sep 17 00:00:00 2001 From: Matt Warman Date: Sat, 16 May 2015 10:57:29 -0400 Subject: [PATCH 5/6] Created GreetingController unit tests with Mockito mocked dependencies. --- .../ws/web/api/GreetingController.java | 9 +- .../leanstacks/ws/AbstractControllerTest.java | 11 + .../web/api/GreetingControllerMocksTest.java | 290 ++++++++++++++++++ .../ws/web/api/GreetingControllerTest.java | 7 +- 4 files changed, 311 insertions(+), 6 deletions(-) create mode 100644 src/test/java/com/leanstacks/ws/web/api/GreetingControllerMocksTest.java diff --git a/src/main/java/com/leanstacks/ws/web/api/GreetingController.java b/src/main/java/com/leanstacks/ws/web/api/GreetingController.java index 418ad1f..8b3608e 100644 --- a/src/main/java/com/leanstacks/ws/web/api/GreetingController.java +++ b/src/main/java/com/leanstacks/ws/web/api/GreetingController.java @@ -51,7 +51,7 @@ public class GreetingController extends BaseController { value = "/api/greetings", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity> getGreetings() throws Exception { + public ResponseEntity> getGreetings() { logger.info("> getGreetings"); Collection greetings = greetingService.findAll(); @@ -80,8 +80,7 @@ public ResponseEntity> getGreetings() throws Exception { value = "/api/greetings/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity getGreeting(@PathVariable Long id) - throws Exception { + public ResponseEntity getGreeting(@PathVariable Long id) { logger.info("> getGreeting"); Greeting greeting = greetingService.findOne(id); @@ -117,7 +116,7 @@ public ResponseEntity getGreeting(@PathVariable Long id) consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity createGreeting( - @RequestBody Greeting greeting) throws Exception { + @RequestBody Greeting greeting) { logger.info("> createGreeting"); Greeting savedGreeting = greetingService.create(greeting); @@ -152,7 +151,7 @@ public ResponseEntity createGreeting( consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity updateGreeting( - @RequestBody Greeting greeting) throws Exception { + @RequestBody Greeting greeting) { logger.info("> updateGreeting"); Greeting updatedGreeting = greetingService.update(greeting); diff --git a/src/test/java/com/leanstacks/ws/AbstractControllerTest.java b/src/test/java/com/leanstacks/ws/AbstractControllerTest.java index 87da316..3e9fd10 100644 --- a/src/test/java/com/leanstacks/ws/AbstractControllerTest.java +++ b/src/test/java/com/leanstacks/ws/AbstractControllerTest.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.leanstacks.ws.web.api.BaseController; /** * This class extends the functionality of AbstractTest. AbstractControllerTest @@ -38,6 +39,16 @@ protected void setUp() { mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); } + /** + * Prepares the test class for execution of web tests. Builds a MockMvc + * instance using standalone configuration facilitating the injection of + * Mockito resources into the controller class. + * @param controller A controller object to be tested. + */ + protected void setUp(BaseController controller) { + mvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + /** * Maps an Object into a JSON String. Uses a Jackson ObjectMapper. * @param obj The Object to map. diff --git a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerMocksTest.java b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerMocksTest.java new file mode 100644 index 0000000..bcb1a8d --- /dev/null +++ b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerMocksTest.java @@ -0,0 +1,290 @@ +package com.leanstacks.ws.web.api; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +import com.leanstacks.ws.AbstractControllerTest; +import com.leanstacks.ws.model.Greeting; +import com.leanstacks.ws.service.EmailService; +import com.leanstacks.ws.service.GreetingService; + +/** + * Unit tests for the GreetingController using Mockito mocks and spies. + * + * These tests utilize the Mockito framework objects to simulate interaction + * with back-end components. The controller methods are invoked directly + * bypassing the Spring MVC mappings. Back-end components are mocked and + * injected into the controller. Mockito spies and verifications are performed + * ensuring controller behaviors. + * + * @author Matt Warman + */ +@Transactional +public class GreetingControllerMocksTest extends AbstractControllerTest { + + /** + * A mocked GreetingService + */ + @Mock + private GreetingService greetingService; + + /** + * A mocked EmailService + */ + @Mock + private EmailService emailService; + + /** + * A GreetingController instance with @Mock components injected + * into it. + */ + @InjectMocks + private GreetingController greetingController; + + /** + * Setup each test method. Initialize Mockito mock and spy objects. Scan for + * Mockito annotations. + */ + @Before + public void setUp() { + // Initialize Mockito annotated components + MockitoAnnotations.initMocks(this); + // Prepare the Spring MVC Mock components for standalone testing + setUp(greetingController); + } + + @Test + public void testGetGreetings() throws Exception { + + // Create some test data + Collection list = getEntityListStubData(); + + // Stub the GreetingService.findAll method return value + when(greetingService.findAll()).thenReturn(list); + + // Perform the behavior being tested + String uri = "/api/greetings"; + + MvcResult result = mvc.perform( + MockMvcRequestBuilders.get(uri).accept( + MediaType.APPLICATION_JSON)).andReturn(); + + // Extract the response status and body + String content = result.getResponse().getContentAsString(); + int status = result.getResponse().getStatus(); + + // Verify the GreetingService.findAll method was invoked once + verify(greetingService, times(1)).findAll(); + + // Perform standard JUnit assertions on the response + Assert.assertEquals("failure - expected HTTP status 200", 200, status); + Assert.assertTrue( + "failure - expected HTTP response body to have a value", + content.trim().length() > 0); + + } + + @Test + public void testGetGreeting() throws Exception { + + // Create some test data + Long id = new Long(1); + Greeting entity = getEntityStubData(); + + // Stub the GreetingService.findOne method return value + when(greetingService.findOne(id)).thenReturn(entity); + + // Perform the behavior being tested + String uri = "/api/greetings/{id}"; + + MvcResult result = mvc.perform( + MockMvcRequestBuilders.get(uri, id).accept( + MediaType.APPLICATION_JSON)).andReturn(); + + // Extract the response status and body + String content = result.getResponse().getContentAsString(); + int status = result.getResponse().getStatus(); + + // Verify the GreetingService.findOne method was invoked once + verify(greetingService, times(1)).findOne(id); + + // Perform standard JUnit assertions on the test results + Assert.assertEquals("failure - expected HTTP status 200", 200, status); + Assert.assertTrue( + "failure - expected HTTP response body to have a value", + content.trim().length() > 0); + } + + @Test + public void testGetGreetingNotFound() throws Exception { + + // Create some test data + Long id = Long.MAX_VALUE; + + // Stub the GreetingService.findOne method return value + when(greetingService.findOne(id)).thenReturn(null); + + // Perform the behavior being tested + String uri = "/api/greetings/{id}"; + + MvcResult result = mvc.perform( + MockMvcRequestBuilders.get(uri, id).accept( + MediaType.APPLICATION_JSON)).andReturn(); + + // Extract the response status and body + String content = result.getResponse().getContentAsString(); + int status = result.getResponse().getStatus(); + + // Verify the GreetingService.findOne method was invoked once + verify(greetingService, times(1)).findOne(id); + + // Perform standard JUnit assertions on the test results + Assert.assertEquals("failure - expected HTTP status 404", 404, status); + Assert.assertTrue("failure - expected HTTP response body to be empty", + content.trim().length() == 0); + + } + + @Test + public void testCreateGreeting() throws Exception { + + // Create some test data + Greeting entity = getEntityStubData(); + + // Stub the GreetingService.create method return value + when(greetingService.create(any(Greeting.class))).thenReturn(entity); + + // Perform the behavior being tested + String uri = "/api/greetings"; + String inputJson = super.mapToJson(entity); + + MvcResult result = mvc.perform( + MockMvcRequestBuilders.post(uri) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON).content(inputJson)) + .andReturn(); + + // Extract the response status and body + String content = result.getResponse().getContentAsString(); + int status = result.getResponse().getStatus(); + + // Verify the GreetingService.create method was invoked once + verify(greetingService, times(1)).create(any(Greeting.class)); + + // Perform standard JUnit assertions on the test results + Assert.assertEquals("failure - expected HTTP status 201", 201, status); + Assert.assertTrue( + "failure - expected HTTP response body to have a value", + content.trim().length() > 0); + + Greeting createdEntity = super.mapFromJson(content, Greeting.class); + + Assert.assertNotNull("failure - expected entity not null", + createdEntity); + Assert.assertNotNull("failure - expected id attribute not null", + createdEntity.getId()); + Assert.assertEquals("failure - expected text attribute match", + entity.getText(), createdEntity.getText()); + } + + @Test + public void testUpdateGreeting() throws Exception { + + // Create some test data + Greeting entity = getEntityStubData(); + entity.setText(entity.getText() + " test"); + Long id = new Long(1); + + // Stub the GreetingService.update method return value + when(greetingService.update(any(Greeting.class))).thenReturn(entity); + + // Perform the behavior being tested + String uri = "/api/greetings/{id}"; + String inputJson = super.mapToJson(entity); + + MvcResult result = mvc.perform( + MockMvcRequestBuilders.put(uri, id) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON).content(inputJson)) + .andReturn(); + + // Extract the response status and body + String content = result.getResponse().getContentAsString(); + int status = result.getResponse().getStatus(); + + // Verify the GreetingService.update method was invoked once + verify(greetingService, times(1)).update(any(Greeting.class)); + + // Perform standard JUnit assertions on the test results + Assert.assertEquals("failure - expected HTTP status 200", 200, status); + Assert.assertTrue( + "failure - expected HTTP response body to have a value", + content.trim().length() > 0); + + Greeting updatedEntity = super.mapFromJson(content, Greeting.class); + + Assert.assertNotNull("failure - expected entity not null", + updatedEntity); + Assert.assertEquals("failure - expected id attribute unchanged", + entity.getId(), updatedEntity.getId()); + Assert.assertEquals("failure - expected text attribute match", + entity.getText(), updatedEntity.getText()); + + } + + @Test + public void testDeleteGreeting() throws Exception { + + // Create some test data + Long id = new Long(1); + + // Perform the behavior being tested + String uri = "/api/greetings/{id}"; + + MvcResult result = mvc.perform(MockMvcRequestBuilders.delete(uri, id)) + .andReturn(); + + // Extract the response status and body + String content = result.getResponse().getContentAsString(); + int status = result.getResponse().getStatus(); + + // Verify the GreetingService.delete method was invoked once + verify(greetingService, times(1)).delete(id); + + // Perform standard JUnit assertions on the test results + Assert.assertEquals("failure - expected HTTP status 204", 204, status); + Assert.assertTrue("failure - expected HTTP response body to be empty", + content.trim().length() == 0); + + } + + private Collection getEntityListStubData() { + Collection list = new ArrayList(); + list.add(getEntityStubData()); + return list; + } + + private Greeting getEntityStubData() { + Greeting entity = new Greeting(); + entity.setId(1L); + entity.setText("hello"); + return entity; + } + +} diff --git a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java index f08bdac..53a7b70 100644 --- a/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java +++ b/src/test/java/com/leanstacks/ws/web/api/GreetingControllerTest.java @@ -14,7 +14,12 @@ import com.leanstacks.ws.service.GreetingService; /** - * Unit tests for the GreetingController. + * Unit tests for the GreetingController using Spring MVC Mocks. + * + * These tests utilize the Spring MVC Mock objects to simulate sending actual + * HTTP requests to the Controller component. This test ensures that the + * RequestMappings are configured correctly. Also, these tests ensure that the + * request and response bodies are serialized as expected. * * @author Matt Warman */ From bfd95dbc382c585e4d8bb24a47d6b1cc6d8654fa Mon Sep 17 00:00:00 2001 From: Matt Warman Date: Sat, 16 May 2015 11:05:57 -0400 Subject: [PATCH 6/6] ReadMe --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8187814..d1b7411 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,14 @@ The project provides examples of Spring Security integration. The web service e The project demonstrates how to use Spring Profiles to activate (or deactivate) application components and configuration. The profiles illustrated are: batch, hsqldb, and mysql. #### Unit Tests -The project contains unit test examples for standard components such as business services or batch beans and examples for the web service endpoints using Mock objects. +The project contains unit test examples for standard components such as business services or batch beans and examples for the web service endpoints using Mock objects. Perform complete end-to-end testing with Spring MVC mocking or leverage Mockito to stub or spy business components. #### Actuator Monitoring and Management The project illustrates the use of Spring Boot Actuator for application monitoring and management. The application demonstrates the recording of custom metrics. Also, custom Maven project attributes are incorporated into the Actuator info endpoint. +#### API Documentation Generator +The project includes [Springfox](http://springfox.github.io/springfox/) Swagger integration to automatically generate API docs for the RESTful web service endpoints. This feature may be activated using the *"docs"* Spring profile. + ## Languages This project is authored in Java.