Change PetTypeFormatter and Validator to use DTO

This commit is contained in:
PEDSF 2020-10-10 12:22:17 +02:00
parent 4dd08b9c5b
commit 66c486891a
6 changed files with 263 additions and 73 deletions

View file

@ -22,10 +22,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid; import javax.validation.Valid;
@ -53,7 +50,7 @@ class OwnerController {
this.visitService = visitService; this.visitService = visitService;
} }
@InitBinder @InitBinder("owner")
public void setAllowedFields(WebDataBinder dataBinder) { public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id"); dataBinder.setDisallowedFields("id");
} }
@ -66,7 +63,7 @@ class OwnerController {
} }
@PostMapping("/owners/new") @PostMapping("/owners/new")
public String processCreationForm(@Valid OwnerDTO owner, BindingResult result) { public String processCreationForm(@ModelAttribute("owner") @Valid OwnerDTO owner, BindingResult result) {
if (result.hasErrors()) { if (result.hasErrors()) {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} }

View file

@ -16,7 +16,7 @@
package org.springframework.samples.petclinic.controller; package org.springframework.samples.petclinic.controller;
import org.springframework.samples.petclinic.dto.*; import org.springframework.samples.petclinic.dto.*;
import org.springframework.samples.petclinic.validator.PetValidator; import org.springframework.samples.petclinic.validator.PetDTOValidator;
import org.springframework.samples.petclinic.service.*; import org.springframework.samples.petclinic.service.*;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
@ -69,7 +69,7 @@ class PetController {
@InitBinder("pet") @InitBinder("pet")
public void initPetBinder(WebDataBinder dataBinder) { public void initPetBinder(WebDataBinder dataBinder) {
dataBinder.setValidator(new PetValidator()); dataBinder.setValidator(new PetDTOValidator());
} }
@GetMapping("/pets/new") @GetMapping("/pets/new")
@ -81,11 +81,12 @@ class PetController {
} }
@PostMapping("/pets/new") @PostMapping("/pets/new")
public String processCreationForm(OwnerDTO ownerDTO, @Valid PetDTO pet, BindingResult result, ModelMap model) { public String processCreationForm(@ModelAttribute("owner") OwnerDTO owner,
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && ownerDTO.getPet(pet.getName(), true) != null) { @ModelAttribute("pet") @Valid PetDTO pet, BindingResult result, ModelMap model) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
result.rejectValue("name", "duplicate", "already exists"); result.rejectValue("name", "duplicate", "already exists");
} }
ownerDTO.addPet(pet); owner.addPet(pet);
if (result.hasErrors()) { if (result.hasErrors()) {
model.put("pet", pet); model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; return VIEWS_PETS_CREATE_OR_UPDATE_FORM;

View file

@ -0,0 +1,65 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.validator;
import org.springframework.samples.petclinic.dto.PetDTO;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
/**
* <code>Validator</code> for <code>PetDTO</code> forms.
* <p>
* We're not using Bean Validation annotations here because it is easier to define such
* validation rule in Java.
* </p>
*
* @author Ken Krebs
* @author Juergen Hoeller
*/
public class PetDTOValidator implements Validator {
private static final String REQUIRED = "required";
@Override
public void validate(Object obj, Errors errors) {
PetDTO pet = (PetDTO) obj;
String name = pet.getName();
// name validation
if (!StringUtils.hasLength(name)) {
errors.rejectValue("name", REQUIRED, REQUIRED);
}
// type validation
if (pet.isNew() && pet.getType() == null) {
errors.rejectValue("type", REQUIRED, REQUIRED);
}
// birth date validation
if (pet.getBirthDate() == null) {
errors.rejectValue("birthDate", REQUIRED, REQUIRED);
}
}
/**
* This Validator validates *just* Pet instances
*/
@Override
public boolean supports(Class<?> clazz) {
return PetDTO.class.isAssignableFrom(clazz);
}
}

View file

@ -37,7 +37,7 @@ public class PetValidator implements Validator {
@Override @Override
public void validate(Object obj, Errors errors) { public void validate(Object obj, Errors errors) {
PetDTO pet = (PetDTO) obj; Pet pet = (Pet) obj;
String name = pet.getName(); String name = pet.getName();
// name validation // name validation
if (!StringUtils.hasLength(name)) { if (!StringUtils.hasLength(name)) {

View file

@ -93,111 +93,141 @@ class OwnerControllerTests {
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner")) mockMvc.perform(get("/owners/new"))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs") mockMvc.perform(post("/owners/new")
.param("address", "123 Caramel Street").param("city", "London").param("telephone", "01316761638")) .param("firstName", "Joe")
.andExpect(status().is3xxRedirection()); .param("lastName", "Bloggs")
.param("address", "123 Caramel Street")
.param("city", "London")
.param("telephone", "01316761638"))
.andExpect(status().is3xxRedirection());
} }
@Test @Test
void testProcessCreationFormHasErrors() throws Exception { void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform( mockMvc.perform(post("/owners/new")
post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London")) .param("firstName", "Joe")
.andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner")) .param("lastName", "Bloggs")
.andExpect(model().attributeHasFieldErrors("owner", "address")) .param("city", "London"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) .andExpect(status().isOk())
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone"))
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testInitFindForm() throws Exception { void testInitFindForm() throws Exception {
mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner")) mockMvc.perform(get("/owners/find"))
.andExpect(view().name("owners/findOwners")); .andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(view().name("owners/findOwners"));
} }
@Test @Test
void testProcessFindFormSuccess() throws Exception { void testProcessFindFormSuccess() throws Exception {
given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new OwnerDTO())); given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new OwnerDTO()));
mockMvc.perform(get("/owners")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList"));
mockMvc.perform(get("/owners"))
.andExpect(status().isOk())
.andExpect(view().name("owners/ownersList"));
} }
@Test @Test
void testProcessFindFormByLastName() throws Exception { void testProcessFindFormByLastName() throws Exception {
given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george)); given(this.owners.findByLastName(george.getLastName())).willReturn(Lists.newArrayList(george));
mockMvc.perform(get("/owners").param("lastName", "Franklin")).andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID)); mockMvc.perform(get("/owners")
.param("lastName", "Franklin"))
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID));
} }
@Test @Test
void testProcessFindFormNoOwnersFound() throws Exception { void testProcessFindFormNoOwnersFound() throws Exception {
mockMvc.perform(get("/owners").param("lastName", "Unknown Surname")).andExpect(status().isOk()) mockMvc.perform(get("/owners")
.andExpect(model().attributeHasFieldErrors("owner", "lastName")) .param("lastName", "Unknown Surname"))
.andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound")) .andExpect(status().isOk())
.andExpect(view().name("owners/findOwners")); .andExpect(model().attributeHasFieldErrors("owner", "lastName"))
.andExpect(model().attributeHasFieldErrorCode("owner", "lastName", "notFound"))
.andExpect(view().name("owners/findOwners"));
} }
@Test @Test
void testInitUpdateOwnerForm() throws Exception { void testInitUpdateOwnerForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).andExpect(status().isOk()) mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID))
.andExpect(model().attributeExists("owner")) .andExpect(status().isOk())
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attributeExists("owner"))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testProcessUpdateOwnerFormSuccess() throws Exception { void testProcessUpdateOwnerFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe") mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID)
.param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London") .param("firstName", "Joe")
.param("telephone", "01616291589")).andExpect(status().is3xxRedirection()) .param("lastName", "Bloggs")
.andExpect(view().name("redirect:/owners/{ownerId}")); .param("address", "123 Caramel Street")
.param("city", "London")
.param("telephone", "01616291589"))
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}"));
} }
@Test @Test
void testProcessUpdateOwnerFormHasErrors() throws Exception { void testProcessUpdateOwnerFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe") mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID)
.param("lastName", "Bloggs").param("city", "London")).andExpect(status().isOk()) .param("firstName", "Joe")
.andExpect(model().attributeHasErrors("owner")) .param("lastName", "Bloggs")
.andExpect(model().attributeHasFieldErrors("owner", "address")) .param("city", "London"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone")) .andExpect(status().isOk())
.andExpect(view().name("owners/createOrUpdateOwnerForm")); .andExpect(model().attributeHasErrors("owner"))
.andExpect(model().attributeHasFieldErrors("owner", "address"))
.andExpect(model().attributeHasFieldErrors("owner", "telephone"))
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
} }
@Test @Test
void testShowOwner() throws Exception { void testShowOwner() throws Exception {
mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk()) mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID))
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(status().isOk())
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.andExpect(model().attribute("owner", hasProperty("pets", not(empty())))) .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<PetDTO>>() { .andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<PetDTO>>() {
@Override @Override
public boolean matches(Object item) { public boolean matches(Object item) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<PetDTO> pets = (List<PetDTO>) item; List<PetDTO> pets = (List<PetDTO>) item;
PetDTO pet = pets.get(0); PetDTO pet = pets.get(0);
if (pet.getVisits().isEmpty()) { if (pet.getVisits().isEmpty()) {
return false; return false;
} }
return true;
}
@Override return true;
public void describeTo(Description description) { }
description.appendText("Max did not have any visits");
} @Override
}))).andExpect(view().name("owners/ownerDetails")); public void describeTo(Description description) {
description.appendText("Max did not have any visits");
}
})))
.andExpect(view().name("owners/ownerDetails"));
} }
} }

View file

@ -0,0 +1,97 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.samples.petclinic.owner;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.samples.petclinic.dto.PetTypeDTO;
import org.springframework.samples.petclinic.service.PetTypeService;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
/**
* Test class for {@link PetTypeFormatter}
*
* @author Colin But
*/
@ExtendWith(MockitoExtension.class)
class PetTypeDTOFormatterTests {
@Mock
private PetTypeService petTypeService;
private PetTypeFormatter petTypeFormatter;
@BeforeEach
void setup() {
this.petTypeFormatter = new PetTypeFormatter(petTypeService);
}
@Test
void testPrint() {
PetTypeDTO petType = new PetTypeDTO();
petType.setName("Hamster");
String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH);
assertThat(petTypeName).isEqualTo("Hamster");
}
@Test
void shouldParse() throws ParseException {
given(this.petTypeService.findPetTypes()).willReturn(makePetTypes());
PetTypeDTO petType = petTypeFormatter.parse("Bird", Locale.ENGLISH);
assertThat(petType.getName()).isEqualTo("Bird");
}
@Test
void shouldThrowParseException() throws ParseException {
given(this.petTypeService.findPetTypes()).willReturn(makePetTypes());
Assertions.assertThrows(ParseException.class, () -> {
petTypeFormatter.parse("Fish", Locale.ENGLISH);
});
}
/**
* Helper method to produce some sample pet types just for test purpose
* @return {@link Collection} of {@link PetType}
*/
private List<PetTypeDTO> makePetTypes() {
List<PetTypeDTO> petTypes = new ArrayList<>();
petTypes.add(new PetTypeDTO() {
{
setName("Dog");
}
});
petTypes.add(new PetTypeDTO() {
{
setName("Bird");
}
});
return petTypes;
}
}