-
Notifications
You must be signed in to change notification settings - Fork 5
Chapter 9 Lab
In the previous demo, we created an exception handler to handle MyCustomException thrown from the
HeartbeatController. This lab will build off the previous and you will write your own exception handling method.
- Create a new class named
BadRequestExceptionto be thrown when you encounter a bad request. It is a good idea for this class to take aStringparameter so that you can customize the error message.
-
Our
PokemonControllerTestclass does not load our Spring container, and therefore has no idea that the global exception handler exists. In the past, this problem was solved by writing controller tests as integration tests (like the DAO tests, using@ContextConfigurationto load the Spring container). Today, we have access toMockMvcBuilders.standaloneSetup(...). Following the invocation of.standaloneSetup(...), you can provide your controller advice class(es) (the global exception handler) via.setControllerAdvice(...). Instantiate a new instance of your global exception handler and provide it to this setter. -
Inside
PokemonControllerTest, add the following test cases. These test cases will verify that the appropriate validation is added to your controller and that useful error messages are thrown in each case. The lab is complete when all of your unit tests pass. Do note that we took some liberty in assuming that you have a constant instance ofObjectMapperby the name ofOBJECT_MAPPERavailable for converting request objects into JSON.@Test public void test_update_nullId_shouldThrowError() throws Exception { final PokemonUpdateRequest request = new PokemonUpdateRequest(); request.setHp(2); request.setAttack(3); this.mockMvc.perform(post("/pokemon/update") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon ID cannot be null"))); } @Test public void test_update_nullHp_shouldThrowError() throws Exception { final PokemonUpdateRequest request = new PokemonUpdateRequest(); request.setId(1); request.setAttack(3); this.mockMvc.perform(post("/pokemon/update") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon HP cannot be null"))); } @Test public void test_update_hpTooSmall_shouldThrowError() throws Exception { final PokemonUpdateRequest request = new PokemonUpdateRequest(); request.setId(1); request.setHp(0); request.setAttack(3); this.mockMvc.perform(post("/pokemon/update") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon HP must be between 1 and 100 (inclusive)"))); } @Test public void test_update_hpTooLarge_shouldThrowError() throws Exception { final PokemonUpdateRequest request = new PokemonUpdateRequest(); request.setId(1); request.setHp(101); request.setAttack(3); this.mockMvc.perform(post("/pokemon/update") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon HP must be between 1 and 100 (inclusive)"))); } @Test public void test_update_nullAttack_shouldThrowError() throws Exception { final PokemonUpdateRequest request = new PokemonUpdateRequest(); request.setId(1); request.setHp(2); this.mockMvc.perform(post("/pokemon/update") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon attack cannot be null"))); } @Test public void test_update_attackTooSmall_shouldThrowError() throws Exception { final PokemonUpdateRequest request = new PokemonUpdateRequest(); request.setId(1); request.setHp(2); request.setAttack(0); this.mockMvc.perform(post("/pokemon/update") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon attack must be greater than 0"))); } @Test public void test_create_nullHp_shouldThrowError() throws Exception { final PokemonCreateRequest request = new PokemonCreateRequest(); request.setAttack(3); this.mockMvc.perform(post("/pokemon/create") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon HP cannot be null"))); } @Test public void test_create_hpTooSmall_shouldThrowError() throws Exception { final PokemonCreateRequest request = new PokemonCreateRequest(); request.setHp(0); request.setAttack(3); this.mockMvc.perform(post("/pokemon/create") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon HP must be between 1 and 100 (inclusive)"))); } @Test public void test_create_hpTooLarge_shouldThrowError() throws Exception { final PokemonCreateRequest request = new PokemonCreateRequest(); request.setHp(101); request.setAttack(3); this.mockMvc.perform(post("/pokemon/create") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon HP must be between 1 and 100 (inclusive)"))); } @Test public void test_create_nullAttack_shouldThrowError() throws Exception { final PokemonCreateRequest request = new PokemonCreateRequest(); request.setHp(2); this.mockMvc.perform(post("/pokemon/create") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon attack cannot be null"))); } @Test public void test_create_attackTooSmall_shouldThrowError() throws Exception { final PokemonCreateRequest request = new PokemonCreateRequest(); request.setHp(2); request.setAttack(0); this.mockMvc.perform(post("/pokemon/create") .content(OBJECT_MAPPER.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().is(HttpStatus.BAD_REQUEST.value())) .andExpect(jsonPath("$.title", is("Bad request"))) .andExpect(jsonPath("$.message", is("Pokemon attack must be greater than 0"))); }
In this lab you're going to replace all existing validation in Java with bean validation.
- Remove the validation we added inside
PokemonController(from Lab 1). Delete the custom exception class (BadRequestException) and its exception handler method that we added in the last lab. Keep the test cases though. Our goal is to make those tests pass.
-
Add the following dependencies to your pom. Note that
validation-apiprovides the annotations themselves andhibernate-validatorprovides the business logic necessary for the annotations to do something useful.<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.0.0.GA</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>4.3.2.Final</version> <scope>runtime</scope> </dependency>
-
Inside
PokemonController, add@Validnext to the existing RequestBody annotation inside the create and update methods. -
Add all necessary bean validation annotations in
PokemonCreateRequestandPokemonUpdateRequest- You can find a list of annotations here: http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html
- Be sure to add error messages in the annotation parameters
-
Write an exception handler for when the request is invalid
- Run one of your controller tests to figure out which exception gets thrown.
- Add a handler method for this exception. You may need to use your debugger to learn how to properly format the error response.
In this lab you're going to store all your message descriptors for PokemonCreateRequest and PokemonUpdateRequest
into a properties file. You will also create and use custom annotations for @Attackand @HitPoints.
- Create a new file inside
src/main/resourcesnamedValidationMessages.properties.
- Move the error descriptors within
PokemonCreateRequestandPokemonUpdateRequestinto the new properties file.-
Example: Your first line inside
ValidationMessages.propertiesmight look like this:validation.id.null=Pokemon ID cannot be nullThe associated validation in
PokemonUpdateRequestwill now look like this:public class PokemonUpdateRequest { @NotNull(message = "{validation.id.null}") private Integer id;
-
You should end up with at least 5 properties. Again, this is just for
PokemonCreateRequestandPokemonUpdateRequest. -
Verify that your tests still run.
-
- Create and use custom annotations for
@Attackand@HitPoints.- Use the example provided in class if you need help.