Apply spring-format plugin

This commit is contained in:
Dave Syer 2020-01-03 11:22:05 +00:00
parent 82cb521d63
commit 4e1f87407d
36 changed files with 1128 additions and 1168 deletions

15
pom.xml
View file

@ -28,6 +28,7 @@
<wro4j.version>1.8.0</wro4j.version> <wro4j.version>1.8.0</wro4j.version>
<jacoco.version>0.8.5</jacoco.version> <jacoco.version>0.8.5</jacoco.version>
<spring-format.version>0.0.17</spring-format.version>
</properties> </properties>
@ -130,6 +131,20 @@
<build> <build>
<plugins> <plugins>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
<version>${spring-format.version}</version>
<!-- run ./mvnw spring-javaformat:apply to apply -->
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>

View file

@ -28,8 +28,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(proxyBeanMethods = false) @SpringBootApplication(proxyBeanMethods = false)
public class PetClinicApplication { public class PetClinicApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(PetClinicApplication.class, args); SpringApplication.run(PetClinicApplication.class, args);
} }
} }

View file

@ -31,20 +31,21 @@ import javax.persistence.MappedSuperclass;
*/ */
@MappedSuperclass @MappedSuperclass
public class BaseEntity implements Serializable { public class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
public Integer getId() { @Id
return id; @GeneratedValue(strategy = GenerationType.IDENTITY)
} private Integer id;
public void setId(Integer id) { public Integer getId() {
this.id = id; return id;
} }
public boolean isNew() { public void setId(Integer id) {
return this.id == null; this.id = id;
} }
public boolean isNew() {
return this.id == null;
}
} }

View file

@ -18,10 +18,9 @@ package org.springframework.samples.petclinic.model;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
/** /**
* Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as a base class for objects * Simple JavaBean domain object adds a name property to <code>BaseEntity</code>. Used as
* needing these properties. * a base class for objects needing these properties.
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -29,20 +28,20 @@ import javax.persistence.MappedSuperclass;
@MappedSuperclass @MappedSuperclass
public class NamedEntity extends BaseEntity { public class NamedEntity extends BaseEntity {
@Column(name = "name") @Column(name = "name")
private String name; private String name;
public String getName() { public String getName() {
return this.name; return this.name;
} }
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
@Override @Override
public String toString() { public String toString() {
return this.getName(); return this.getName();
} }
} }

View file

@ -27,28 +27,28 @@ import javax.validation.constraints.NotEmpty;
@MappedSuperclass @MappedSuperclass
public class Person extends BaseEntity { public class Person extends BaseEntity {
@Column(name = "first_name") @Column(name = "first_name")
@NotEmpty @NotEmpty
private String firstName; private String firstName;
@Column(name = "last_name") @Column(name = "last_name")
@NotEmpty @NotEmpty
private String lastName; private String lastName;
public String getFirstName() { public String getFirstName() {
return this.firstName; return this.firstName;
} }
public void setFirstName(String firstName) { public void setFirstName(String firstName) {
this.firstName = firstName; this.firstName = firstName;
} }
public String getLastName() { public String getLastName() {
return this.lastName; return this.lastName;
} }
public void setLastName(String lastName) { public void setLastName(String lastName) {
this.lastName = lastName; this.lastName = lastName;
} }
} }

View file

@ -18,4 +18,3 @@
* The classes in this package represent utilities used by the domain. * The classes in this package represent utilities used by the domain.
*/ */
package org.springframework.samples.petclinic.model; package org.springframework.samples.petclinic.model;

View file

@ -45,108 +45,106 @@ import org.springframework.samples.petclinic.model.Person;
@Entity @Entity
@Table(name = "owners") @Table(name = "owners")
public class Owner extends Person { public class Owner extends Person {
@Column(name = "address")
@NotEmpty
private String address;
@Column(name = "city") @Column(name = "address")
@NotEmpty @NotEmpty
private String city; private String address;
@Column(name = "telephone") @Column(name = "city")
@NotEmpty @NotEmpty
@Digits(fraction = 0, integer = 10) private String city;
private String telephone;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner") @Column(name = "telephone")
private Set<Pet> pets; @NotEmpty
@Digits(fraction = 0, integer = 10)
private String telephone;
public String getAddress() { @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
return this.address; private Set<Pet> pets;
}
public void setAddress(String address) { public String getAddress() {
this.address = address; return this.address;
} }
public String getCity() { public void setAddress(String address) {
return this.city; this.address = address;
} }
public void setCity(String city) { public String getCity() {
this.city = city; return this.city;
} }
public String getTelephone() { public void setCity(String city) {
return this.telephone; this.city = city;
} }
public void setTelephone(String telephone) { public String getTelephone() {
this.telephone = telephone; return this.telephone;
} }
protected Set<Pet> getPetsInternal() { public void setTelephone(String telephone) {
if (this.pets == null) { this.telephone = telephone;
this.pets = new HashSet<>(); }
}
return this.pets;
}
protected void setPetsInternal(Set<Pet> pets) { protected Set<Pet> getPetsInternal() {
this.pets = pets; if (this.pets == null) {
} this.pets = new HashSet<>();
}
return this.pets;
}
public List<Pet> getPets() { protected void setPetsInternal(Set<Pet> pets) {
List<Pet> sortedPets = new ArrayList<>(getPetsInternal()); this.pets = pets;
PropertyComparator.sort(sortedPets, }
new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedPets);
}
public void addPet(Pet pet) { public List<Pet> getPets() {
if (pet.isNew()) { List<Pet> sortedPets = new ArrayList<>(getPetsInternal());
getPetsInternal().add(pet); PropertyComparator.sort(sortedPets, new MutableSortDefinition("name", true, true));
} return Collections.unmodifiableList(sortedPets);
pet.setOwner(this); }
}
/** public void addPet(Pet pet) {
* Return the Pet with the given name, or null if none found for this Owner. if (pet.isNew()) {
* getPetsInternal().add(pet);
* @param name to test }
* @return true if pet name is already in use pet.setOwner(this);
*/ }
public Pet getPet(String name) {
return getPet(name, false);
}
/** /**
* Return the Pet with the given name, or null if none found for this Owner. * Return the Pet with the given name, or null if none found for this Owner.
* * @param name to test
* @param name to test * @return true if pet name is already in use
* @return true if pet name is already in use */
*/ public Pet getPet(String name) {
public Pet getPet(String name, boolean ignoreNew) { return getPet(name, false);
name = name.toLowerCase(); }
for (Pet pet : getPetsInternal()) {
if (!ignoreNew || !pet.isNew()) {
String compName = pet.getName();
compName = compName.toLowerCase();
if (compName.equals(name)) {
return pet;
}
}
}
return null;
}
@Override /**
public String toString() { * Return the Pet with the given name, or null if none found for this Owner.
return new ToStringCreator(this) * @param name to test
* @return true if pet name is already in use
*/
public Pet getPet(String name, boolean ignoreNew) {
name = name.toLowerCase();
for (Pet pet : getPetsInternal()) {
if (!ignoreNew || !pet.isNew()) {
String compName = pet.getName();
compName = compName.toLowerCase();
if (compName.equals(name)) {
return pet;
}
}
}
return null;
}
@Override
public String toString() {
return new ToStringCreator(this)
.append("id", this.getId()).append("new", this.isNew()).append("lastName", this.getLastName())
.append("firstName", this.getFirstName()).append("address", this.address).append("city", this.city)
.append("telephone", this.telephone).toString();
}
.append("id", this.getId()).append("new", this.isNew())
.append("lastName", this.getLastName())
.append("firstName", this.getFirstName()).append("address", this.address)
.append("city", this.city).append("telephone", this.telephone).toString();
}
} }

View file

@ -39,102 +39,107 @@ import java.util.Map;
@Controller @Controller
class OwnerController { class OwnerController {
private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm"; private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
private final OwnerRepository owners;
private VisitRepository visits;
private final OwnerRepository owners;
public OwnerController(OwnerRepository clinicService, VisitRepository visits) { private VisitRepository visits;
this.owners = clinicService;
this.visits = visits;
}
@InitBinder public OwnerController(OwnerRepository clinicService, VisitRepository visits) {
public void setAllowedFields(WebDataBinder dataBinder) { this.owners = clinicService;
dataBinder.setDisallowedFields("id"); this.visits = visits;
} }
@GetMapping("/owners/new") @InitBinder
public String initCreationForm(Map<String, Object> model) { public void setAllowedFields(WebDataBinder dataBinder) {
Owner owner = new Owner(); dataBinder.setDisallowedFields("id");
model.put("owner", owner); }
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
}
@PostMapping("/owners/new") @GetMapping("/owners/new")
public String processCreationForm(@Valid Owner owner, BindingResult result) { public String initCreationForm(Map<String, Object> model) {
if (result.hasErrors()) { Owner owner = new Owner();
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; model.put("owner", owner);
} else { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
this.owners.save(owner); }
return "redirect:/owners/" + owner.getId();
}
}
@GetMapping("/owners/find") @PostMapping("/owners/new")
public String initFindForm(Map<String, Object> model) { public String processCreationForm(@Valid Owner owner, BindingResult result) {
model.put("owner", new Owner()); if (result.hasErrors()) {
return "owners/findOwners"; return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
} }
else {
this.owners.save(owner);
return "redirect:/owners/" + owner.getId();
}
}
@GetMapping("/owners") @GetMapping("/owners/find")
public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) { public String initFindForm(Map<String, Object> model) {
model.put("owner", new Owner());
return "owners/findOwners";
}
// allow parameterless GET request for /owners to return all records @GetMapping("/owners")
if (owner.getLastName() == null) { public String processFindForm(Owner owner, BindingResult result, Map<String, Object> model) {
owner.setLastName(""); // empty string signifies broadest possible search
}
// find owners by last name // allow parameterless GET request for /owners to return all records
Collection<Owner> results = this.owners.findByLastName(owner.getLastName()); if (owner.getLastName() == null) {
if (results.isEmpty()) { owner.setLastName(""); // empty string signifies broadest possible search
// no owners found }
result.rejectValue("lastName", "notFound", "not found");
return "owners/findOwners";
} else if (results.size() == 1) {
// 1 owner found
owner = results.iterator().next();
return "redirect:/owners/" + owner.getId();
} else {
// multiple owners found
model.put("selections", results);
return "owners/ownersList";
}
}
@GetMapping("/owners/{ownerId}/edit") // find owners by last name
public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) { Collection<Owner> results = this.owners.findByLastName(owner.getLastName());
Owner owner = this.owners.findById(ownerId); if (results.isEmpty()) {
model.addAttribute(owner); // no owners found
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; result.rejectValue("lastName", "notFound", "not found");
} return "owners/findOwners";
}
else if (results.size() == 1) {
// 1 owner found
owner = results.iterator().next();
return "redirect:/owners/" + owner.getId();
}
else {
// multiple owners found
model.put("selections", results);
return "owners/ownersList";
}
}
@PostMapping("/owners/{ownerId}/edit") @GetMapping("/owners/{ownerId}/edit")
public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result, @PathVariable("ownerId") int ownerId) { public String initUpdateOwnerForm(@PathVariable("ownerId") int ownerId, Model model) {
if (result.hasErrors()) { Owner owner = this.owners.findById(ownerId);
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM; model.addAttribute(owner);
} else { return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
owner.setId(ownerId); }
this.owners.save(owner);
return "redirect:/owners/{ownerId}";
}
}
/** @PostMapping("/owners/{ownerId}/edit")
* Custom handler for displaying an owner. public String processUpdateOwnerForm(@Valid Owner owner, BindingResult result,
* @PathVariable("ownerId") int ownerId) {
* @param ownerId the ID of the owner to display if (result.hasErrors()) {
* @return a ModelMap with the model attributes for the view return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
*/ }
@GetMapping("/owners/{ownerId}") else {
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) { owner.setId(ownerId);
ModelAndView mav = new ModelAndView("owners/ownerDetails"); this.owners.save(owner);
Owner owner = this.owners.findById(ownerId); return "redirect:/owners/{ownerId}";
for (Pet pet : owner.getPets()) { }
pet.setVisitsInternal(visits.findByPetId(pet.getId())); }
}
mav.addObject(owner); /**
return mav; * Custom handler for displaying an owner.
} * @param ownerId the ID of the owner to display
* @return a ModelMap with the model attributes for the view
*/
@GetMapping("/owners/{ownerId}")
public ModelAndView showOwner(@PathVariable("ownerId") int ownerId) {
ModelAndView mav = new ModelAndView("owners/ownerDetails");
Owner owner = this.owners.findById(ownerId);
for (Pet pet : owner.getPets()) {
pet.setVisitsInternal(visits.findByPetId(pet.getId()));
}
mav.addObject(owner);
return mav;
}
} }

View file

@ -23,9 +23,10 @@ import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Owner</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Owner</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -34,31 +35,30 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
public interface OwnerRepository extends Repository<Owner, Integer> { public interface OwnerRepository extends Repository<Owner, Integer> {
/** /**
* Retrieve {@link Owner}s from the data store by last name, returning all owners * Retrieve {@link Owner}s from the data store by last name, returning all owners
* whose last name <i>starts</i> with the given name. * whose last name <i>starts</i> with the given name.
* @param lastName Value to search for * @param lastName Value to search for
* @return a Collection of matching {@link Owner}s (or an empty Collection if none * @return a Collection of matching {@link Owner}s (or an empty Collection if none
* found) * found)
*/ */
@Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%") @Query("SELECT DISTINCT owner FROM Owner owner left join fetch owner.pets WHERE owner.lastName LIKE :lastName%")
@Transactional(readOnly = true) @Transactional(readOnly = true)
Collection<Owner> findByLastName(@Param("lastName") String lastName); Collection<Owner> findByLastName(@Param("lastName") String lastName);
/** /**
* Retrieve an {@link Owner} from the data store by id. * Retrieve an {@link Owner} from the data store by id.
* @param id the id to search for * @param id the id to search for
* @return the {@link Owner} if found * @return the {@link Owner} if found
*/ */
@Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id") @Query("SELECT owner FROM Owner owner left join fetch owner.pets WHERE owner.id =:id")
@Transactional(readOnly = true) @Transactional(readOnly = true)
Owner findById(@Param("id") Integer id); Owner findById(@Param("id") Integer id);
/**
* Save an {@link Owner} to the data store, either inserting or updating it.
* @param owner the {@link Owner} to save
*/
void save(Owner owner);
/**
* Save an {@link Owner} to the data store, either inserting or updating it.
* @param owner the {@link Owner} to save
*/
void save(Owner owner);
} }

View file

@ -48,66 +48,65 @@ import org.springframework.samples.petclinic.visit.Visit;
@Table(name = "pets") @Table(name = "pets")
public class Pet extends NamedEntity { public class Pet extends NamedEntity {
@Column(name = "birth_date") @Column(name = "birth_date")
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate; private LocalDate birthDate;
@ManyToOne @ManyToOne
@JoinColumn(name = "type_id") @JoinColumn(name = "type_id")
private PetType type; private PetType type;
@ManyToOne @ManyToOne
@JoinColumn(name = "owner_id") @JoinColumn(name = "owner_id")
private Owner owner; private Owner owner;
@Transient @Transient
private Set<Visit> visits = new LinkedHashSet<>(); private Set<Visit> visits = new LinkedHashSet<>();
public void setBirthDate(LocalDate birthDate) { public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate; this.birthDate = birthDate;
} }
public LocalDate getBirthDate() { public LocalDate getBirthDate() {
return this.birthDate; return this.birthDate;
} }
public PetType getType() { public PetType getType() {
return this.type; return this.type;
} }
public void setType(PetType type) { public void setType(PetType type) {
this.type = type; this.type = type;
} }
public Owner getOwner() { public Owner getOwner() {
return this.owner; return this.owner;
} }
protected void setOwner(Owner owner) { protected void setOwner(Owner owner) {
this.owner = owner; this.owner = owner;
} }
protected Set<Visit> getVisitsInternal() { protected Set<Visit> getVisitsInternal() {
if (this.visits == null) { if (this.visits == null) {
this.visits = new HashSet<>(); this.visits = new HashSet<>();
} }
return this.visits; return this.visits;
} }
protected void setVisitsInternal(Collection<Visit> visits) { protected void setVisitsInternal(Collection<Visit> visits) {
this.visits = new LinkedHashSet<>(visits); this.visits = new LinkedHashSet<>(visits);
} }
public List<Visit> getVisits() { public List<Visit> getVisits() {
List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal()); List<Visit> sortedVisits = new ArrayList<>(getVisitsInternal());
PropertyComparator.sort(sortedVisits, PropertyComparator.sort(sortedVisits, new MutableSortDefinition("date", false, false));
new MutableSortDefinition("date", false, false)); return Collections.unmodifiableList(sortedVisits);
return Collections.unmodifiableList(sortedVisits); }
}
public void addVisit(Visit visit) { public void addVisit(Visit visit) {
getVisitsInternal().add(visit); getVisitsInternal().add(visit);
visit.setPetId(this.getId()); visit.setPetId(this.getId());
} }
} }

View file

@ -34,76 +34,80 @@ import java.util.Collection;
@RequestMapping("/owners/{ownerId}") @RequestMapping("/owners/{ownerId}")
class PetController { class PetController {
private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm"; private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
private final PetRepository pets;
private final OwnerRepository owners;
public PetController(PetRepository pets, OwnerRepository owners) { private final PetRepository pets;
this.pets = pets;
this.owners = owners;
}
@ModelAttribute("types") private final OwnerRepository owners;
public Collection<PetType> populatePetTypes() {
return this.pets.findPetTypes();
}
@ModelAttribute("owner") public PetController(PetRepository pets, OwnerRepository owners) {
public Owner findOwner(@PathVariable("ownerId") int ownerId) { this.pets = pets;
return this.owners.findById(ownerId); this.owners = owners;
} }
@InitBinder("owner") @ModelAttribute("types")
public void initOwnerBinder(WebDataBinder dataBinder) { public Collection<PetType> populatePetTypes() {
dataBinder.setDisallowedFields("id"); return this.pets.findPetTypes();
} }
@InitBinder("pet") @ModelAttribute("owner")
public void initPetBinder(WebDataBinder dataBinder) { public Owner findOwner(@PathVariable("ownerId") int ownerId) {
dataBinder.setValidator(new PetValidator()); return this.owners.findById(ownerId);
} }
@GetMapping("/pets/new") @InitBinder("owner")
public String initCreationForm(Owner owner, ModelMap model) { public void initOwnerBinder(WebDataBinder dataBinder) {
Pet pet = new Pet(); dataBinder.setDisallowedFields("id");
owner.addPet(pet); }
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
@PostMapping("/pets/new") @InitBinder("pet")
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) { public void initPetBinder(WebDataBinder dataBinder) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null){ dataBinder.setValidator(new PetValidator());
result.rejectValue("name", "duplicate", "already exists"); }
}
owner.addPet(pet);
if (result.hasErrors()) {
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else {
this.pets.save(pet);
return "redirect:/owners/{ownerId}";
}
}
@GetMapping("/pets/{petId}/edit") @GetMapping("/pets/new")
public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) { public String initCreationForm(Owner owner, ModelMap model) {
Pet pet = this.pets.findById(petId); Pet pet = new Pet();
model.put("pet", pet); owner.addPet(pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; model.put("pet", pet);
} return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
@PostMapping("/pets/{petId}/edit") @PostMapping("/pets/new")
public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) { public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result, ModelMap model) {
if (result.hasErrors()) { if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null) {
pet.setOwner(owner); result.rejectValue("name", "duplicate", "already exists");
model.put("pet", pet); }
return VIEWS_PETS_CREATE_OR_UPDATE_FORM; owner.addPet(pet);
} else { if (result.hasErrors()) {
owner.addPet(pet); model.put("pet", pet);
this.pets.save(pet); return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
return "redirect:/owners/{ownerId}"; }
} else {
} this.pets.save(pet);
return "redirect:/owners/{ownerId}";
}
}
@GetMapping("/pets/{petId}/edit")
public String initUpdateForm(@PathVariable("petId") int petId, ModelMap model) {
Pet pet = this.pets.findById(petId);
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
@PostMapping("/pets/{petId}/edit")
public String processUpdateForm(@Valid Pet pet, BindingResult result, Owner owner, ModelMap model) {
if (result.hasErrors()) {
pet.setOwner(owner);
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
else {
owner.addPet(pet);
this.pets.save(pet);
return "redirect:/owners/{ownerId}";
}
}
} }

View file

@ -22,9 +22,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Pet</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Pet</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -33,27 +34,26 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
public interface PetRepository extends Repository<Pet, Integer> { public interface PetRepository extends Repository<Pet, Integer> {
/** /**
* Retrieve all {@link PetType}s from the data store. * Retrieve all {@link PetType}s from the data store.
* @return a Collection of {@link PetType}s. * @return a Collection of {@link PetType}s.
*/ */
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name") @Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
@Transactional(readOnly = true) @Transactional(readOnly = true)
List<PetType> findPetTypes(); List<PetType> findPetTypes();
/** /**
* Retrieve a {@link Pet} from the data store by id. * Retrieve a {@link Pet} from the data store by id.
* @param id the id to search for * @param id the id to search for
* @return the {@link Pet} if found * @return the {@link Pet} if found
*/ */
@Transactional(readOnly = true) @Transactional(readOnly = true)
Pet findById(Integer id); Pet findById(Integer id);
/** /**
* Save a {@link Pet} to the data store, either inserting or updating it. * Save a {@link Pet} to the data store, either inserting or updating it.
* @param pet the {@link Pet} to save * @param pet the {@link Pet} to save
*/ */
void save(Pet pet); void save(Pet pet);
} }

View file

@ -21,8 +21,7 @@ import javax.persistence.Table;
import org.springframework.samples.petclinic.model.NamedEntity; import org.springframework.samples.petclinic.model.NamedEntity;
/** /**
* @author Juergen Hoeller * @author Juergen Hoeller Can be Cat, Dog, Hamster...
* Can be Cat, Dog, Hamster...
*/ */
@Entity @Entity
@Table(name = "types") @Table(name = "types")

View file

@ -24,9 +24,10 @@ import org.springframework.format.Formatter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** /**
* Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting from Spring 3.0, Formatters have * Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting
* come as an improvement in comparison to legacy PropertyEditors. See the following links for more details: - The * from Spring 3.0, Formatters have come as an improvement in comparison to legacy
* Spring ref doc: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format * PropertyEditors. See the following links for more details: - The Spring ref doc:
* https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#format
* *
* @author Mark Fisher * @author Mark Fisher
* @author Juergen Hoeller * @author Juergen Hoeller
@ -35,28 +36,27 @@ import org.springframework.stereotype.Component;
@Component @Component
public class PetTypeFormatter implements Formatter<PetType> { public class PetTypeFormatter implements Formatter<PetType> {
private final PetRepository pets; private final PetRepository pets;
@Autowired
public PetTypeFormatter(PetRepository pets) {
this.pets = pets;
}
@Autowired @Override
public PetTypeFormatter(PetRepository pets) { public String print(PetType petType, Locale locale) {
this.pets = pets; return petType.getName();
} }
@Override @Override
public String print(PetType petType, Locale locale) { public PetType parse(String text, Locale locale) throws ParseException {
return petType.getName(); Collection<PetType> findPetTypes = this.pets.findPetTypes();
} for (PetType type : findPetTypes) {
if (type.getName().equals(text)) {
@Override return type;
public PetType parse(String text, Locale locale) throws ParseException { }
Collection<PetType> findPetTypes = this.pets.findPetTypes(); }
for (PetType type : findPetTypes) { throw new ParseException("type not found: " + text, 0);
if (type.getName().equals(text)) { }
return type;
}
}
throw new ParseException("type not found: " + text, 0);
}
} }

View file

@ -22,7 +22,8 @@ import org.springframework.validation.Validator;
/** /**
* <code>Validator</code> for <code>Pet</code> forms. * <code>Validator</code> for <code>Pet</code> forms.
* <p> * <p>
* We're not using Bean Validation annotations here because it is easier to define such validation rule in Java. * We're not using Bean Validation annotations here because it is easier to define such
* validation rule in Java.
* </p> * </p>
* *
* @author Ken Krebs * @author Ken Krebs
@ -30,35 +31,34 @@ import org.springframework.validation.Validator;
*/ */
public class PetValidator implements Validator { public class PetValidator implements Validator {
private static final String REQUIRED = "required"; private static final String REQUIRED = "required";
@Override @Override
public void validate(Object obj, Errors errors) { public void validate(Object obj, Errors errors) {
Pet pet = (Pet) 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)) {
errors.rejectValue("name", REQUIRED, REQUIRED); errors.rejectValue("name", REQUIRED, REQUIRED);
} }
// type validation // type validation
if (pet.isNew() && pet.getType() == null) { if (pet.isNew() && pet.getType() == null) {
errors.rejectValue("type", REQUIRED, REQUIRED); errors.rejectValue("type", REQUIRED, REQUIRED);
} }
// birth date validation // birth date validation
if (pet.getBirthDate() == null) { if (pet.getBirthDate() == null) {
errors.rejectValue("birthDate", REQUIRED, REQUIRED); errors.rejectValue("birthDate", REQUIRED, REQUIRED);
} }
} }
/**
* This Validator validates *just* Pet instances
*/
@Override
public boolean supports(Class<?> clazz) {
return Pet.class.isAssignableFrom(clazz);
}
/**
* This Validator validates *just* Pet instances
*/
@Override
public boolean supports(Class<?> clazz) {
return Pet.class.isAssignableFrom(clazz);
}
} }

View file

@ -40,55 +40,53 @@ import org.springframework.web.bind.annotation.PostMapping;
@Controller @Controller
class VisitController { class VisitController {
private final VisitRepository visits; private final VisitRepository visits;
private final PetRepository pets;
private final PetRepository pets;
public VisitController(VisitRepository visits, PetRepository pets) { public VisitController(VisitRepository visits, PetRepository pets) {
this.visits = visits; this.visits = visits;
this.pets = pets; this.pets = pets;
} }
@InitBinder @InitBinder
public void setAllowedFields(WebDataBinder dataBinder) { public void setAllowedFields(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id"); dataBinder.setDisallowedFields("id");
} }
/** /**
* Called before each and every @RequestMapping annotated method. * Called before each and every @RequestMapping annotated method. 2 goals: - Make sure
* 2 goals: * we always have fresh data - Since we do not use the session scope, make sure that
* - Make sure we always have fresh data * Pet object always has an id (Even though id is not part of the form fields)
* - Since we do not use the session scope, make sure that Pet object always has an id * @param petId
* (Even though id is not part of the form fields) * @return Pet
* */
* @param petId @ModelAttribute("visit")
* @return Pet public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) {
*/ Pet pet = this.pets.findById(petId);
@ModelAttribute("visit") pet.setVisitsInternal(this.visits.findByPetId(petId));
public Visit loadPetWithVisit(@PathVariable("petId") int petId, Map<String, Object> model) { model.put("pet", pet);
Pet pet = this.pets.findById(petId); Visit visit = new Visit();
pet.setVisitsInternal(this.visits.findByPetId(petId)); pet.addVisit(visit);
model.put("pet", pet); return visit;
Visit visit = new Visit(); }
pet.addVisit(visit);
return visit;
}
// Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before initNewVisitForm is called
@GetMapping("/owners/*/pets/{petId}/visits/new") @GetMapping("/owners/*/pets/{petId}/visits/new")
public String initNewVisitForm(@PathVariable("petId") int petId, Map<String, Object> model) { public String initNewVisitForm(@PathVariable("petId") int petId, Map<String, Object> model) {
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} }
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called // Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new") @PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
public String processNewVisitForm(@Valid Visit visit, BindingResult result) { public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
if (result.hasErrors()) { if (result.hasErrors()) {
return "pets/createOrUpdateVisitForm"; return "pets/createOrUpdateVisitForm";
} else { }
this.visits.save(visit); else {
return "redirect:/owners/{ownerId}"; this.visits.save(visit);
} return "redirect:/owners/{ownerId}";
} }
}
} }

View file

@ -32,24 +32,24 @@ import org.springframework.context.annotation.Configuration;
@EnableCaching @EnableCaching
class CacheConfiguration { class CacheConfiguration {
@Bean @Bean
public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() { public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {
return cm -> { return cm -> {
cm.createCache("vets", cacheConfiguration()); cm.createCache("vets", cacheConfiguration());
}; };
} }
/** /**
* Create a simple configuration that enable statistics via the JCache programmatic * Create a simple configuration that enable statistics via the JCache programmatic
* configuration API. * configuration API.
* <p> * <p>
* Within the configuration object that is provided by the JCache API standard, there * Within the configuration object that is provided by the JCache API standard, there
* is only a very limited set of configuration options. The really relevant * is only a very limited set of configuration options. The really relevant
* configuration options (like the size limit) must be set via a configuration * configuration options (like the size limit) must be set via a configuration
* mechanism that is provided by the selected JCache implementation. * mechanism that is provided by the selected JCache implementation.
*/ */
private javax.cache.configuration.Configuration<Object, Object> cacheConfiguration() { private javax.cache.configuration.Configuration<Object, Object> cacheConfiguration() {
return new MutableConfiguration<>().setStatisticsEnabled(true); return new MutableConfiguration<>().setStatisticsEnabled(true);
} }
} }

View file

@ -28,10 +28,10 @@ import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
class CrashController { class CrashController {
@GetMapping("/oups") @GetMapping("/oups")
public String triggerException() { public String triggerException() {
throw new RuntimeException("Expected: controller used to showcase what " throw new RuntimeException(
+ "happens when an exception is thrown"); "Expected: controller used to showcase what " + "happens when an exception is thrown");
} }
} }

View file

@ -16,15 +16,15 @@
package org.springframework.samples.petclinic.system; package org.springframework.samples.petclinic.system;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@Controller @Controller
class WelcomeController { class WelcomeController {
@GetMapping("/") @GetMapping("/")
public String welcome() { public String welcome() {
return "welcome"; return "welcome";
} }
} }

View file

@ -45,35 +45,35 @@ import org.springframework.samples.petclinic.model.Person;
@Table(name = "vets") @Table(name = "vets")
public class Vet extends Person { public class Vet extends Person {
@ManyToMany(fetch = FetchType.EAGER) @ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"), inverseJoinColumns = @JoinColumn(name = "specialty_id")) @JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
private Set<Specialty> specialties; inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set<Specialty> specialties;
protected Set<Specialty> getSpecialtiesInternal() { protected Set<Specialty> getSpecialtiesInternal() {
if (this.specialties == null) { if (this.specialties == null) {
this.specialties = new HashSet<>(); this.specialties = new HashSet<>();
} }
return this.specialties; return this.specialties;
} }
protected void setSpecialtiesInternal(Set<Specialty> specialties) { protected void setSpecialtiesInternal(Set<Specialty> specialties) {
this.specialties = specialties; this.specialties = specialties;
} }
@XmlElement @XmlElement
public List<Specialty> getSpecialties() { public List<Specialty> getSpecialties() {
List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal()); List<Specialty> sortedSpecs = new ArrayList<>(getSpecialtiesInternal());
PropertyComparator.sort(sortedSpecs, PropertyComparator.sort(sortedSpecs, new MutableSortDefinition("name", true, true));
new MutableSortDefinition("name", true, true)); return Collections.unmodifiableList(sortedSpecs);
return Collections.unmodifiableList(sortedSpecs); }
}
public int getNrOfSpecialties() { public int getNrOfSpecialties() {
return getSpecialtiesInternal().size(); return getSpecialtiesInternal().size();
} }
public void addSpecialty(Specialty specialty) { public void addSpecialty(Specialty specialty) {
getSpecialtiesInternal().add(specialty); getSpecialtiesInternal().add(specialty);
} }
} }

View file

@ -30,29 +30,29 @@ import java.util.Map;
@Controller @Controller
class VetController { class VetController {
private final VetRepository vets; private final VetRepository vets;
public VetController(VetRepository clinicService) { public VetController(VetRepository clinicService) {
this.vets = clinicService; this.vets = clinicService;
} }
@GetMapping("/vets.html") @GetMapping("/vets.html")
public String showVetList(Map<String, Object> model) { public String showVetList(Map<String, Object> model) {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for Object-Xml mapping // objects so it is simpler for Object-Xml mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList().addAll(this.vets.findAll());
model.put("vets", vets); model.put("vets", vets);
return "vets/vetList"; return "vets/vetList";
} }
@GetMapping({ "/vets" }) @GetMapping({ "/vets" })
public @ResponseBody Vets showResourcesVetList() { public @ResponseBody Vets showResourcesVetList() {
// Here we are returning an object of type 'Vets' rather than a collection of Vet // Here we are returning an object of type 'Vets' rather than a collection of Vet
// objects so it is simpler for JSon/Object mapping // objects so it is simpler for JSon/Object mapping
Vets vets = new Vets(); Vets vets = new Vets();
vets.getVetList().addAll(this.vets.findAll()); vets.getVetList().addAll(this.vets.findAll());
return vets; return vets;
} }
} }

View file

@ -23,9 +23,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
/** /**
* Repository class for <code>Vet</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Vet</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -34,14 +35,12 @@ import org.springframework.transaction.annotation.Transactional;
*/ */
public interface VetRepository extends Repository<Vet, Integer> { public interface VetRepository extends Repository<Vet, Integer> {
/** /**
* Retrieve all <code>Vet</code>s from the data store. * Retrieve all <code>Vet</code>s from the data store.
* * @return a <code>Collection</code> of <code>Vet</code>s
* @return a <code>Collection</code> of <code>Vet</code>s */
*/ @Transactional(readOnly = true)
@Transactional(readOnly = true) @Cacheable("vets")
@Cacheable("vets") Collection<Vet> findAll() throws DataAccessException;
Collection<Vet> findAll() throws DataAccessException;
} }

View file

@ -22,22 +22,22 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
/** /**
* Simple domain object representing a list of veterinarians. Mostly here to be used for the 'vets' {@link * Simple domain object representing a list of veterinarians. Mostly here to be used for
* org.springframework.web.servlet.view.xml.MarshallingView}. * the 'vets' {@link org.springframework.web.servlet.view.xml.MarshallingView}.
* *
* @author Arjen Poutsma * @author Arjen Poutsma
*/ */
@XmlRootElement @XmlRootElement
public class Vets { public class Vets {
private List<Vet> vets; private List<Vet> vets;
@XmlElement @XmlElement
public List<Vet> getVetList() { public List<Vet> getVetList() {
if (vets == null) { if (vets == null) {
vets = new ArrayList<>(); vets = new ArrayList<>();
} }
return vets; return vets;
} }
} }

View file

@ -35,46 +35,46 @@ import org.springframework.samples.petclinic.model.BaseEntity;
@Table(name = "visits") @Table(name = "visits")
public class Visit extends BaseEntity { public class Visit extends BaseEntity {
@Column(name = "visit_date") @Column(name = "visit_date")
@DateTimeFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date; private LocalDate date;
@NotEmpty @NotEmpty
@Column(name = "description") @Column(name = "description")
private String description; private String description;
@Column(name = "pet_id") @Column(name = "pet_id")
private Integer petId; private Integer petId;
/** /**
* Creates a new instance of Visit for the current date * Creates a new instance of Visit for the current date
*/ */
public Visit() { public Visit() {
this.date = LocalDate.now(); this.date = LocalDate.now();
} }
public LocalDate getDate() { public LocalDate getDate() {
return this.date; return this.date;
} }
public void setDate(LocalDate date) { public void setDate(LocalDate date) {
this.date = date; this.date = date;
} }
public String getDescription() { public String getDescription() {
return this.description; return this.description;
} }
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
public Integer getPetId() { public Integer getPetId() {
return this.petId; return this.petId;
} }
public void setPetId(Integer petId) { public void setPetId(Integer petId) {
this.petId = petId; this.petId = petId;
} }
} }

View file

@ -22,9 +22,10 @@ import org.springframework.data.repository.Repository;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Repository class for <code>Visit</code> domain objects All method names are compliant with Spring Data naming * Repository class for <code>Visit</code> domain objects All method names are compliant
* conventions so this interface can easily be extended for Spring Data. * with Spring Data naming conventions so this interface can easily be extended for Spring
* See: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation * Data. See:
* https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
* *
* @author Ken Krebs * @author Ken Krebs
* @author Juergen Hoeller * @author Juergen Hoeller
@ -33,14 +34,13 @@ import org.springframework.samples.petclinic.model.BaseEntity;
*/ */
public interface VisitRepository extends Repository<Visit, Integer> { public interface VisitRepository extends Repository<Visit, Integer> {
/** /**
* Save a <code>Visit</code> to the data store, either inserting or updating it. * Save a <code>Visit</code> to the data store, either inserting or updating it.
* * @param visit the <code>Visit</code> to save
* @param visit the <code>Visit</code> to save * @see BaseEntity#isNew
* @see BaseEntity#isNew */
*/ void save(Visit visit) throws DataAccessException;
void save(Visit visit) throws DataAccessException;
List<Visit> findByPetId(Integer petId); List<Visit> findByPetId(Integer petId);
} }

View file

@ -24,12 +24,13 @@ import org.springframework.samples.petclinic.vet.VetRepository;
@SpringBootTest @SpringBootTest
class PetclinicIntegrationTests { class PetclinicIntegrationTests {
@Autowired @Autowired
private VetRepository vets; private VetRepository vets;
@Test
void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
@Test
void testFindAll() throws Exception {
vets.findAll();
vets.findAll(); // served from cache
}
} }

View file

@ -34,28 +34,27 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class ValidatorTests { class ValidatorTests {
private Validator createValidator() { private Validator createValidator() {
LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
localValidatorFactoryBean.afterPropertiesSet(); localValidatorFactoryBean.afterPropertiesSet();
return localValidatorFactoryBean; return localValidatorFactoryBean;
} }
@Test @Test
void shouldNotValidateWhenFirstNameEmpty() { void shouldNotValidateWhenFirstNameEmpty() {
LocaleContextHolder.setLocale(Locale.ENGLISH); LocaleContextHolder.setLocale(Locale.ENGLISH);
Person person = new Person(); Person person = new Person();
person.setFirstName(""); person.setFirstName("");
person.setLastName("smith"); person.setLastName("smith");
Validator validator = createValidator(); Validator validator = createValidator();
Set<ConstraintViolation<Person>> constraintViolations = validator Set<ConstraintViolation<Person>> constraintViolations = validator.validate(person);
.validate(person);
assertThat(constraintViolations).hasSize(1); assertThat(constraintViolations).hasSize(1);
ConstraintViolation<Person> violation = constraintViolations.iterator().next(); ConstraintViolation<Person> violation = constraintViolations.iterator().next();
assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName"); assertThat(violation.getPropertyPath().toString()).isEqualTo("firstName");
assertThat(violation.getMessage()).isEqualTo("must not be empty"); assertThat(violation.getMessage()).isEqualTo("must not be empty");
} }
} }

View file

@ -51,181 +51,149 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebMvcTest(OwnerController.class) @WebMvcTest(OwnerController.class)
class OwnerControllerTests { class OwnerControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@MockBean @MockBean
private VisitRepository visits; private VisitRepository visits;
private Owner george; private Owner george;
@BeforeEach @BeforeEach
void setup() { void setup() {
george = new Owner(); george = new Owner();
george.setId(TEST_OWNER_ID); george.setId(TEST_OWNER_ID);
george.setFirstName("George"); george.setFirstName("George");
george.setLastName("Franklin"); george.setLastName("Franklin");
george.setAddress("110 W. Liberty St."); george.setAddress("110 W. Liberty St.");
george.setCity("Madison"); george.setCity("Madison");
george.setTelephone("6085551023"); george.setTelephone("6085551023");
Pet max = new Pet(); Pet max = new Pet();
PetType dog = new PetType(); PetType dog = new PetType();
dog.setName("dog"); dog.setName("dog");
max.setId(1); max.setId(1);
max.setType(dog); max.setType(dog);
max.setName("Max"); max.setName("Max");
max.setBirthDate(LocalDate.now()); max.setBirthDate(LocalDate.now());
george.setPetsInternal(Collections.singleton(max)); george.setPetsInternal(Collections.singleton(max));
given(this.owners.findById(TEST_OWNER_ID)).willReturn(george); given(this.owners.findById(TEST_OWNER_ID)).willReturn(george);
Visit visit = new Visit(); Visit visit = new Visit();
visit.setDate(LocalDate.now()); visit.setDate(LocalDate.now());
given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit)); given(this.visits.findByPetId(max.getId())).willReturn(Collections.singletonList(visit));
} }
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/new")) mockMvc.perform(get("/owners/new")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(status().isOk()) .andExpect(view().name("owners/createOrUpdateOwnerForm"));
.andExpect(model().attributeExists("owner")) }
.andExpect(view().name("owners/createOrUpdateOwnerForm"));
}
@Test @Test
void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/new") mockMvc.perform(post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs")
.param("firstName", "Joe") .param("address", "123 Caramel Street").param("city", "London").param("telephone", "01316761638"))
.param("lastName", "Bloggs") .andExpect(status().is3xxRedirection());
.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(post("/owners/new") mockMvc.perform(
.param("firstName", "Joe") post("/owners/new").param("firstName", "Joe").param("lastName", "Bloggs").param("city", "London"))
.param("lastName", "Bloggs") .andExpect(status().isOk()).andExpect(model().attributeHasErrors("owner"))
.param("city", "London") .andExpect(model().attributeHasFieldErrors("owner", "address"))
) .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")) mockMvc.perform(get("/owners/find")).andExpect(status().isOk()).andExpect(model().attributeExists("owner"))
.andExpect(status().isOk()) .andExpect(view().name("owners/findOwners"));
.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 Owner())); given(this.owners.findByLastName("")).willReturn(Lists.newArrayList(george, new Owner()));
mockMvc.perform(get("/owners")) mockMvc.perform(get("/owners")).andExpect(status().isOk()).andExpect(view().name("owners/ownersList"));
.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") mockMvc.perform(get("/owners").param("lastName", "Franklin")).andExpect(status().is3xxRedirection())
.param("lastName", "Franklin") .andExpect(view().name("redirect:/owners/" + TEST_OWNER_ID));
) }
.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") mockMvc.perform(get("/owners").param("lastName", "Unknown Surname")).andExpect(status().isOk())
.param("lastName", "Unknown Surname") .andExpect(model().attributeHasFieldErrors("owner", "lastName"))
) .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)) mockMvc.perform(get("/owners/{ownerId}/edit", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(model().attributeExists("owner"))
.andExpect(model().attributeExists("owner")) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) .andExpect(view().name("owners/createOrUpdateOwnerForm"));
.andExpect(view().name("owners/createOrUpdateOwnerForm")); }
}
@Test @Test
void testProcessUpdateOwnerFormSuccess() throws Exception { void testProcessUpdateOwnerFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("firstName", "Joe") .param("lastName", "Bloggs").param("address", "123 Caramel Street").param("city", "London")
.param("lastName", "Bloggs") .param("telephone", "01616291589")).andExpect(status().is3xxRedirection())
.param("address", "123 Caramel Street") .andExpect(view().name("redirect:/owners/{ownerId}"));
.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) mockMvc.perform(post("/owners/{ownerId}/edit", TEST_OWNER_ID).param("firstName", "Joe")
.param("firstName", "Joe") .param("lastName", "Bloggs").param("city", "London")).andExpect(status().isOk())
.param("lastName", "Bloggs") .andExpect(model().attributeHasErrors("owner"))
.param("city", "London") .andExpect(model().attributeHasFieldErrors("owner", "address"))
) .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)) mockMvc.perform(get("/owners/{ownerId}", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin"))))
.andExpect(model().attribute("owner", hasProperty("lastName", is("Franklin")))) .andExpect(model().attribute("owner", hasProperty("firstName", is("George"))))
.andExpect(model().attribute("owner", hasProperty("firstName", is("George")))) .andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St."))))
.andExpect(model().attribute("owner", hasProperty("address", is("110 W. Liberty St.")))) .andExpect(model().attribute("owner", hasProperty("city", is("Madison"))))
.andExpect(model().attribute("owner", hasProperty("city", is("Madison")))) .andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023"))))
.andExpect(model().attribute("owner", hasProperty("telephone", is("6085551023")))) .andExpect(model().attribute("owner", hasProperty("pets", not(empty()))))
.andExpect(model().attribute("owner", hasProperty("pets", not(empty())))) .andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() {
.andExpect(model().attribute("owner", hasProperty("pets", new BaseMatcher<List<Pet>>() {
@Override @Override
public boolean matches(Object item) { public boolean matches(Object item) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<Pet> pets = (List<Pet>) item; List<Pet> pets = (List<Pet>) item;
Pet pet = pets.get(0); Pet pet = pets.get(0);
if (pet.getVisits().isEmpty()) { if (pet.getVisits().isEmpty()) {
return false; return false;
} }
return true; return true;
} }
@Override @Override
public void describeTo(Description description) { public void describeTo(Description description) {
description.appendText("Max did not have any visits"); description.appendText("Max did not have any visits");
}}))) }
.andExpect(view().name("owners/ownerDetails")); }))).andExpect(view().name("owners/ownerDetails"));
} }
} }

View file

@ -39,97 +39,75 @@ import org.springframework.test.web.servlet.MockMvc;
* @author Colin But * @author Colin But
*/ */
@WebMvcTest(value = PetController.class, @WebMvcTest(value = PetController.class,
includeFilters = @ComponentScan.Filter( includeFilters = @ComponentScan.Filter(value = PetTypeFormatter.class, type = FilterType.ASSIGNABLE_TYPE))
value = PetTypeFormatter.class,
type = FilterType.ASSIGNABLE_TYPE))
class PetControllerTests { class PetControllerTests {
private static final int TEST_OWNER_ID = 1; private static final int TEST_OWNER_ID = 1;
private static final int TEST_PET_ID = 1;
private static final int TEST_PET_ID = 1;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private PetRepository pets; private PetRepository pets;
@MockBean @MockBean
private OwnerRepository owners; private OwnerRepository owners;
@BeforeEach @BeforeEach
void setup() { void setup() {
PetType cat = new PetType(); PetType cat = new PetType();
cat.setId(3); cat.setId(3);
cat.setName("hamster"); cat.setName("hamster");
given(this.pets.findPetTypes()).willReturn(Lists.newArrayList(cat)); given(this.pets.findPetTypes()).willReturn(Lists.newArrayList(cat));
given(this.owners.findById(TEST_OWNER_ID)).willReturn(new Owner()); given(this.owners.findById(TEST_OWNER_ID)).willReturn(new Owner());
given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet()); given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet());
} }
@Test @Test
void testInitCreationForm() throws Exception { void testInitCreationForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)) mockMvc.perform(get("/owners/{ownerId}/pets/new", TEST_OWNER_ID)).andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(view().name("pets/createOrUpdatePetForm")).andExpect(model().attributeExists("pet"));
.andExpect(view().name("pets/createOrUpdatePetForm")) }
.andExpect(model().attributeExists("pet"));
}
@Test @Test
void testProcessCreationFormSuccess() throws Exception { void testProcessCreationFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty")
.param("name", "Betty") .param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection())
.param("type", "hamster") .andExpect(view().name("redirect:/owners/{ownerId}"));
.param("birthDate", "2015-02-12") }
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}"));
}
@Test @Test
void testProcessCreationFormHasErrors() throws Exception { void testProcessCreationFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID) mockMvc.perform(post("/owners/{ownerId}/pets/new", TEST_OWNER_ID).param("name", "Betty").param("birthDate",
.param("name", "Betty") "2015-02-12")).andExpect(model().attributeHasNoErrors("owner"))
.param("birthDate", "2015-02-12") .andExpect(model().attributeHasErrors("pet")).andExpect(model().attributeHasFieldErrors("pet", "type"))
) .andExpect(model().attributeHasFieldErrorCode("pet", "type", "required")).andExpect(status().isOk())
.andExpect(model().attributeHasNoErrors("owner")) .andExpect(view().name("pets/createOrUpdatePetForm"));
.andExpect(model().attributeHasErrors("pet")) }
.andExpect(model().attributeHasFieldErrors("pet", "type"))
.andExpect(model().attributeHasFieldErrorCode("pet", "type", "required"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm"));
}
@Test @Test
void testInitUpdateForm() throws Exception { void testInitUpdateForm() throws Exception {
mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID)) mockMvc.perform(get("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID))
.andExpect(status().isOk()) .andExpect(status().isOk()).andExpect(model().attributeExists("pet"))
.andExpect(model().attributeExists("pet")) .andExpect(view().name("pets/createOrUpdatePetForm"));
.andExpect(view().name("pets/createOrUpdatePetForm")); }
}
@Test @Test
void testProcessUpdateFormSuccess() throws Exception { void testProcessUpdateFormSuccess() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("name", "Betty") .param("type", "hamster").param("birthDate", "2015-02-12")).andExpect(status().is3xxRedirection())
.param("type", "hamster") .andExpect(view().name("redirect:/owners/{ownerId}"));
.param("birthDate", "2015-02-12") }
)
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}"));
}
@Test @Test
void testProcessUpdateFormHasErrors() throws Exception { void testProcessUpdateFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID) mockMvc.perform(post("/owners/{ownerId}/pets/{petId}/edit", TEST_OWNER_ID, TEST_PET_ID).param("name", "Betty")
.param("name", "Betty") .param("birthDate", "2015/02/12")).andExpect(model().attributeHasNoErrors("owner"))
.param("birthDate", "2015/02/12") .andExpect(model().attributeHasErrors("pet")).andExpect(status().isOk())
) .andExpect(view().name("pets/createOrUpdatePetForm"));
.andExpect(model().attributeHasNoErrors("owner")) }
.andExpect(model().attributeHasErrors("pet"))
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdatePetForm"));
}
} }

View file

@ -40,57 +40,56 @@ import static org.mockito.BDDMockito.given;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class PetTypeFormatterTests { class PetTypeFormatterTests {
@Mock @Mock
private PetRepository pets; private PetRepository pets;
private PetTypeFormatter petTypeFormatter; private PetTypeFormatter petTypeFormatter;
@BeforeEach @BeforeEach
void setup() { void setup() {
this.petTypeFormatter = new PetTypeFormatter(pets); this.petTypeFormatter = new PetTypeFormatter(pets);
} }
@Test @Test
void testPrint() { void testPrint() {
PetType petType = new PetType(); PetType petType = new PetType();
petType.setName("Hamster"); petType.setName("Hamster");
String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH); String petTypeName = this.petTypeFormatter.print(petType, Locale.ENGLISH);
assertThat(petTypeName).isEqualTo("Hamster"); assertThat(petTypeName).isEqualTo("Hamster");
} }
@Test @Test
void shouldParse() throws ParseException { void shouldParse() throws ParseException {
given(this.pets.findPetTypes()).willReturn(makePetTypes()); given(this.pets.findPetTypes()).willReturn(makePetTypes());
PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH); PetType petType = petTypeFormatter.parse("Bird", Locale.ENGLISH);
assertThat(petType.getName()).isEqualTo("Bird"); assertThat(petType.getName()).isEqualTo("Bird");
} }
@Test @Test
void shouldThrowParseException() throws ParseException { void shouldThrowParseException() throws ParseException {
given(this.pets.findPetTypes()).willReturn(makePetTypes()); given(this.pets.findPetTypes()).willReturn(makePetTypes());
Assertions.assertThrows(ParseException.class, () -> { Assertions.assertThrows(ParseException.class, () -> {
petTypeFormatter.parse("Fish", Locale.ENGLISH); petTypeFormatter.parse("Fish", Locale.ENGLISH);
}); });
} }
/** /**
* Helper method to produce some sample pet types just for test purpose * Helper method to produce some sample pet types just for test purpose
* * @return {@link Collection} of {@link PetType}
* @return {@link Collection} of {@link PetType} */
*/ private List<PetType> makePetTypes() {
private List<PetType> makePetTypes() { List<PetType> petTypes = new ArrayList<>();
List<PetType> petTypes = new ArrayList<>(); petTypes.add(new PetType() {
petTypes.add(new PetType() { {
{ setName("Dog");
setName("Dog"); }
} });
}); petTypes.add(new PetType() {
petTypes.add(new PetType() { {
{ setName("Bird");
setName("Bird"); }
} });
}); return petTypes;
return petTypes; }
}
} }

View file

@ -39,47 +39,40 @@ import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(VisitController.class) @WebMvcTest(VisitController.class)
class VisitControllerTests { class VisitControllerTests {
private static final int TEST_PET_ID = 1; private static final int TEST_PET_ID = 1;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private VisitRepository visits; private VisitRepository visits;
@MockBean @MockBean
private PetRepository pets; private PetRepository pets;
@BeforeEach @BeforeEach
void init() { void init() {
given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet()); given(this.pets.findById(TEST_PET_ID)).willReturn(new Pet());
} }
@Test @Test
void testInitNewVisitForm() throws Exception { void testInitNewVisitForm() throws Exception {
mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)) mockMvc.perform(get("/owners/*/pets/{petId}/visits/new", TEST_PET_ID)).andExpect(status().isOk())
.andExpect(status().isOk()) .andExpect(view().name("pets/createOrUpdateVisitForm"));
.andExpect(view().name("pets/createOrUpdateVisitForm")); }
}
@Test @Test
void testProcessNewVisitFormSuccess() throws Exception { void testProcessNewVisitFormSuccess() throws Exception {
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George")
.param("name", "George") .param("description", "Visit Description")).andExpect(status().is3xxRedirection())
.param("description", "Visit Description") .andExpect(view().name("redirect:/owners/{ownerId}"));
) }
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/{ownerId}"));
}
@Test @Test
void testProcessNewVisitFormHasErrors() throws Exception { void testProcessNewVisitFormHasErrors() throws Exception {
mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID) mockMvc.perform(post("/owners/*/pets/{petId}/visits/new", TEST_PET_ID).param("name", "George"))
.param("name", "George") .andExpect(model().attributeHasErrors("visit")).andExpect(status().isOk())
) .andExpect(view().name("pets/createOrUpdateVisitForm"));
.andExpect(model().attributeHasErrors("visit")) }
.andExpect(status().isOk())
.andExpect(view().name("pets/createOrUpdateVisitForm"));
}
} }

View file

@ -40,15 +40,24 @@ import org.springframework.transaction.annotation.Transactional;
/** /**
* Integration test of the Service and the Repository layer. * Integration test of the Service and the Repository layer.
* <p> * <p>
* ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided by the Spring * ClinicServiceSpringDataJpaTests subclasses benefit from the following services provided
* TestContext Framework: </p> <ul> <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up * by the Spring TestContext Framework:
* time between test execution.</li> <li><strong>Dependency Injection</strong> of test fixture instances, meaning that * </p>
* we don't need to perform application context lookups. See the use of {@link Autowired @Autowired} on the <code>{@link * <ul>
* ClinicServiceTests#clinicService clinicService}</code> instance variable, which uses autowiring <em>by * <li><strong>Spring IoC container caching</strong> which spares us unnecessary set up
* type</em>. <li><strong>Transaction management</strong>, meaning each test method is executed in its own transaction, * time between test execution.</li>
* which is automatically rolled back by default. Thus, even if tests insert or otherwise change database state, there * <li><strong>Dependency Injection</strong> of test fixture instances, meaning that we
* is no need for a teardown or cleanup script. <li> An {@link org.springframework.context.ApplicationContext * don't need to perform application context lookups. See the use of
* ApplicationContext} is also inherited and can be used for explicit bean lookup if necessary. </li> </ul> * {@link Autowired @Autowired} on the <code>{@link
* ClinicServiceTests#clinicService clinicService}</code> instance variable, which uses
* autowiring <em>by type</em>.
* <li><strong>Transaction management</strong>, meaning each test method is executed in
* its own transaction, which is automatically rolled back by default. Thus, even if tests
* insert or otherwise change database state, there is no need for a teardown or cleanup
* script.
* <li>An {@link org.springframework.context.ApplicationContext ApplicationContext} is
* also inherited and can be used for explicit bean lookup if necessary.</li>
* </ul>
* *
* @author Ken Krebs * @author Ken Krebs
* @author Rod Johnson * @author Rod Johnson
@ -60,159 +69,159 @@ import org.springframework.transaction.annotation.Transactional;
@DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class)) @DataJpaTest(includeFilters = @ComponentScan.Filter(Service.class))
class ClinicServiceTests { class ClinicServiceTests {
@Autowired @Autowired
protected OwnerRepository owners; protected OwnerRepository owners;
@Autowired @Autowired
protected PetRepository pets; protected PetRepository pets;
@Autowired @Autowired
protected VisitRepository visits; protected VisitRepository visits;
@Autowired @Autowired
protected VetRepository vets; protected VetRepository vets;
@Test @Test
void shouldFindOwnersByLastName() { void shouldFindOwnersByLastName() {
Collection<Owner> owners = this.owners.findByLastName("Davis"); Collection<Owner> owners = this.owners.findByLastName("Davis");
assertThat(owners).hasSize(2); assertThat(owners).hasSize(2);
owners = this.owners.findByLastName("Daviss"); owners = this.owners.findByLastName("Daviss");
assertThat(owners).isEmpty(); assertThat(owners).isEmpty();
} }
@Test @Test
void shouldFindSingleOwnerWithPet() { void shouldFindSingleOwnerWithPet() {
Owner owner = this.owners.findById(1); Owner owner = this.owners.findById(1);
assertThat(owner.getLastName()).startsWith("Franklin"); assertThat(owner.getLastName()).startsWith("Franklin");
assertThat(owner.getPets()).hasSize(1); assertThat(owner.getPets()).hasSize(1);
assertThat(owner.getPets().get(0).getType()).isNotNull(); assertThat(owner.getPets().get(0).getType()).isNotNull();
assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat"); assertThat(owner.getPets().get(0).getType().getName()).isEqualTo("cat");
} }
@Test @Test
@Transactional @Transactional
void shouldInsertOwner() { void shouldInsertOwner() {
Collection<Owner> owners = this.owners.findByLastName("Schultz"); Collection<Owner> owners = this.owners.findByLastName("Schultz");
int found = owners.size(); int found = owners.size();
Owner owner = new Owner(); Owner owner = new Owner();
owner.setFirstName("Sam"); owner.setFirstName("Sam");
owner.setLastName("Schultz"); owner.setLastName("Schultz");
owner.setAddress("4, Evans Street"); owner.setAddress("4, Evans Street");
owner.setCity("Wollongong"); owner.setCity("Wollongong");
owner.setTelephone("4444444444"); owner.setTelephone("4444444444");
this.owners.save(owner); this.owners.save(owner);
assertThat(owner.getId().longValue()).isNotEqualTo(0); assertThat(owner.getId().longValue()).isNotEqualTo(0);
owners = this.owners.findByLastName("Schultz"); owners = this.owners.findByLastName("Schultz");
assertThat(owners.size()).isEqualTo(found + 1); assertThat(owners.size()).isEqualTo(found + 1);
} }
@Test @Test
@Transactional @Transactional
void shouldUpdateOwner() { void shouldUpdateOwner() {
Owner owner = this.owners.findById(1); Owner owner = this.owners.findById(1);
String oldLastName = owner.getLastName(); String oldLastName = owner.getLastName();
String newLastName = oldLastName + "X"; String newLastName = oldLastName + "X";
owner.setLastName(newLastName); owner.setLastName(newLastName);
this.owners.save(owner); this.owners.save(owner);
// retrieving new name from database // retrieving new name from database
owner = this.owners.findById(1); owner = this.owners.findById(1);
assertThat(owner.getLastName()).isEqualTo(newLastName); assertThat(owner.getLastName()).isEqualTo(newLastName);
} }
@Test @Test
void shouldFindPetWithCorrectId() { void shouldFindPetWithCorrectId() {
Pet pet7 = this.pets.findById(7); Pet pet7 = this.pets.findById(7);
assertThat(pet7.getName()).startsWith("Samantha"); assertThat(pet7.getName()).startsWith("Samantha");
assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean"); assertThat(pet7.getOwner().getFirstName()).isEqualTo("Jean");
} }
@Test @Test
void shouldFindAllPetTypes() { void shouldFindAllPetTypes() {
Collection<PetType> petTypes = this.pets.findPetTypes(); Collection<PetType> petTypes = this.pets.findPetTypes();
PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1); PetType petType1 = EntityUtils.getById(petTypes, PetType.class, 1);
assertThat(petType1.getName()).isEqualTo("cat"); assertThat(petType1.getName()).isEqualTo("cat");
PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4); PetType petType4 = EntityUtils.getById(petTypes, PetType.class, 4);
assertThat(petType4.getName()).isEqualTo("snake"); assertThat(petType4.getName()).isEqualTo("snake");
} }
@Test @Test
@Transactional @Transactional
void shouldInsertPetIntoDatabaseAndGenerateId() { void shouldInsertPetIntoDatabaseAndGenerateId() {
Owner owner6 = this.owners.findById(6); Owner owner6 = this.owners.findById(6);
int found = owner6.getPets().size(); int found = owner6.getPets().size();
Pet pet = new Pet(); Pet pet = new Pet();
pet.setName("bowser"); pet.setName("bowser");
Collection<PetType> types = this.pets.findPetTypes(); Collection<PetType> types = this.pets.findPetTypes();
pet.setType(EntityUtils.getById(types, PetType.class, 2)); pet.setType(EntityUtils.getById(types, PetType.class, 2));
pet.setBirthDate(LocalDate.now()); pet.setBirthDate(LocalDate.now());
owner6.addPet(pet); owner6.addPet(pet);
assertThat(owner6.getPets().size()).isEqualTo(found + 1); assertThat(owner6.getPets().size()).isEqualTo(found + 1);
this.pets.save(pet); this.pets.save(pet);
this.owners.save(owner6); this.owners.save(owner6);
owner6 = this.owners.findById(6); owner6 = this.owners.findById(6);
assertThat(owner6.getPets().size()).isEqualTo(found + 1); assertThat(owner6.getPets().size()).isEqualTo(found + 1);
// checks that id has been generated // checks that id has been generated
assertThat(pet.getId()).isNotNull(); assertThat(pet.getId()).isNotNull();
} }
@Test @Test
@Transactional @Transactional
void shouldUpdatePetName() throws Exception { void shouldUpdatePetName() throws Exception {
Pet pet7 = this.pets.findById(7); Pet pet7 = this.pets.findById(7);
String oldName = pet7.getName(); String oldName = pet7.getName();
String newName = oldName + "X"; String newName = oldName + "X";
pet7.setName(newName); pet7.setName(newName);
this.pets.save(pet7); this.pets.save(pet7);
pet7 = this.pets.findById(7); pet7 = this.pets.findById(7);
assertThat(pet7.getName()).isEqualTo(newName); assertThat(pet7.getName()).isEqualTo(newName);
} }
@Test @Test
void shouldFindVets() { void shouldFindVets() {
Collection<Vet> vets = this.vets.findAll(); Collection<Vet> vets = this.vets.findAll();
Vet vet = EntityUtils.getById(vets, Vet.class, 3); Vet vet = EntityUtils.getById(vets, Vet.class, 3);
assertThat(vet.getLastName()).isEqualTo("Douglas"); assertThat(vet.getLastName()).isEqualTo("Douglas");
assertThat(vet.getNrOfSpecialties()).isEqualTo(2); assertThat(vet.getNrOfSpecialties()).isEqualTo(2);
assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry"); assertThat(vet.getSpecialties().get(0).getName()).isEqualTo("dentistry");
assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery"); assertThat(vet.getSpecialties().get(1).getName()).isEqualTo("surgery");
} }
@Test @Test
@Transactional @Transactional
void shouldAddNewVisitForPet() { void shouldAddNewVisitForPet() {
Pet pet7 = this.pets.findById(7); Pet pet7 = this.pets.findById(7);
int found = pet7.getVisits().size(); int found = pet7.getVisits().size();
Visit visit = new Visit(); Visit visit = new Visit();
pet7.addVisit(visit); pet7.addVisit(visit);
visit.setDescription("test"); visit.setDescription("test");
this.visits.save(visit); this.visits.save(visit);
this.pets.save(pet7); this.pets.save(pet7);
pet7 = this.pets.findById(7); pet7 = this.pets.findById(7);
assertThat(pet7.getVisits().size()).isEqualTo(found + 1); assertThat(pet7.getVisits().size()).isEqualTo(found + 1);
assertThat(visit.getId()).isNotNull(); assertThat(visit.getId()).isNotNull();
} }
@Test @Test
void shouldFindVisitsByPetId() throws Exception { void shouldFindVisitsByPetId() throws Exception {
Collection<Visit> visits = this.visits.findByPetId(7); Collection<Visit> visits = this.visits.findByPetId(7);
assertThat(visits).hasSize(2); assertThat(visits).hasSize(2);
Visit[] visitArr = visits.toArray(new Visit[visits.size()]); Visit[] visitArr = visits.toArray(new Visit[visits.size()]);
assertThat(visitArr[0].getDate()).isNotNull(); assertThat(visitArr[0].getDate()).isNotNull();
assertThat(visitArr[0].getPetId()).isEqualTo(7); assertThat(visitArr[0].getPetId()).isEqualTo(7);
} }
} }

View file

@ -22,8 +22,8 @@ import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.samples.petclinic.model.BaseEntity; import org.springframework.samples.petclinic.model.BaseEntity;
/** /**
* Utility methods for handling entities. Separate from the BaseEntity class mainly because of dependency on the * Utility methods for handling entities. Separate from the BaseEntity class mainly
* ORM-associated ObjectRetrievalFailureException. * because of dependency on the ORM-associated ObjectRetrievalFailureException.
* *
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @author Sam Brannen
@ -32,23 +32,22 @@ import org.springframework.samples.petclinic.model.BaseEntity;
*/ */
public abstract class EntityUtils { public abstract class EntityUtils {
/** /**
* Look up the entity of the given class with the given id in the given collection. * Look up the entity of the given class with the given id in the given collection.
* * @param entities the collection to search
* @param entities the collection to search * @param entityClass the entity class to look up
* @param entityClass the entity class to look up * @param entityId the entity id to look up
* @param entityId the entity id to look up * @return the found entity
* @return the found entity * @throws ObjectRetrievalFailureException if the entity was not found
* @throws ObjectRetrievalFailureException if the entity was not found */
*/ public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId)
public static <T extends BaseEntity> T getById(Collection<T> entities, Class<T> entityClass, int entityId) throws ObjectRetrievalFailureException {
throws ObjectRetrievalFailureException { for (T entity : entities) {
for (T entity : entities) { if (entity.getId() == entityId && entityClass.isInstance(entity)) {
if (entity.getId() == entityId && entityClass.isInstance(entity)) { return entity;
return entity; }
} }
} throw new ObjectRetrievalFailureException(entityClass, entityId);
throw new ObjectRetrievalFailureException(entityClass, entityId); }
}
} }

View file

@ -38,13 +38,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@WebMvcTest(controllers = CrashController.class) @WebMvcTest(controllers = CrashController.class)
class CrashControllerTests { class CrashControllerTests {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@Test
void testTriggerException() throws Exception {
mockMvc.perform(get("/oups")).andExpect(view().name("exception"))
.andExpect(model().attributeExists("exception")).andExpect(forwardedUrl("exception"))
.andExpect(status().isOk());
}
@Test
void testTriggerException() throws Exception {
mockMvc.perform(get("/oups")).andExpect(view().name("exception"))
.andExpect(model().attributeExists("exception"))
.andExpect(forwardedUrl("exception")).andExpect(status().isOk());
}
} }

View file

@ -40,43 +40,41 @@ import org.springframework.test.web.servlet.ResultActions;
@WebMvcTest(VetController.class) @WebMvcTest(VetController.class)
class VetControllerTests { class VetControllerTests {
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
@MockBean @MockBean
private VetRepository vets; private VetRepository vets;
@BeforeEach @BeforeEach
void setup() { void setup() {
Vet james = new Vet(); Vet james = new Vet();
james.setFirstName("James"); james.setFirstName("James");
james.setLastName("Carter"); james.setLastName("Carter");
james.setId(1); james.setId(1);
Vet helen = new Vet(); Vet helen = new Vet();
helen.setFirstName("Helen"); helen.setFirstName("Helen");
helen.setLastName("Leary"); helen.setLastName("Leary");
helen.setId(2); helen.setId(2);
Specialty radiology = new Specialty(); Specialty radiology = new Specialty();
radiology.setId(1); radiology.setId(1);
radiology.setName("radiology"); radiology.setName("radiology");
helen.addSpecialty(radiology); helen.addSpecialty(radiology);
given(this.vets.findAll()).willReturn(Lists.newArrayList(james, helen)); given(this.vets.findAll()).willReturn(Lists.newArrayList(james, helen));
} }
@Test @Test
void testShowVetListHtml() throws Exception { void testShowVetListHtml() throws Exception {
mockMvc.perform(get("/vets.html")) mockMvc.perform(get("/vets.html")).andExpect(status().isOk()).andExpect(model().attributeExists("vets"))
.andExpect(status().isOk()) .andExpect(view().name("vets/vetList"));
.andExpect(model().attributeExists("vets")) }
.andExpect(view().name("vets/vetList"));
}
@Test @Test
void testShowResourcesVetList() throws Exception { void testShowResourcesVetList() throws Exception {
ResultActions actions = mockMvc.perform(get("/vets") ResultActions actions = mockMvc.perform(get("/vets").accept(MediaType.APPLICATION_JSON))
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); .andExpect(status().isOk());
actions.andExpect(content().contentType(MediaType.APPLICATION_JSON)) actions.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.vetList[0].id").value(1)); .andExpect(jsonPath("$.vetList[0].id").value(1));
} }
} }

View file

@ -25,17 +25,16 @@ import static org.assertj.core.api.Assertions.assertThat;
*/ */
class VetTests { class VetTests {
@Test @Test
void testSerialization() { void testSerialization() {
Vet vet = new Vet(); Vet vet = new Vet();
vet.setFirstName("Zaphod"); vet.setFirstName("Zaphod");
vet.setLastName("Beeblebrox"); vet.setLastName("Beeblebrox");
vet.setId(123); vet.setId(123);
Vet other = (Vet) SerializationUtils Vet other = (Vet) SerializationUtils.deserialize(SerializationUtils.serialize(vet));
.deserialize(SerializationUtils.serialize(vet)); assertThat(other.getFirstName()).isEqualTo(vet.getFirstName());
assertThat(other.getFirstName()).isEqualTo(vet.getFirstName()); assertThat(other.getLastName()).isEqualTo(vet.getLastName());
assertThat(other.getLastName()).isEqualTo(vet.getLastName()); assertThat(other.getId()).isEqualTo(vet.getId());
assertThat(other.getId()).isEqualTo(vet.getId()); }
}
} }