org.springframework.boot
spring-boot-starter-test
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/cache/CacheConfiguration.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/cache/CacheConfiguration.java
new file mode 100644
index 000000000..11a829cbe
--- /dev/null
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/cache/CacheConfiguration.java
@@ -0,0 +1,38 @@
+package guru.springframework.sfgpetclinic.cache;
+
+import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.cache.configuration.MutableConfiguration;
+
+/**
+ * Cache configuration intended for caches providing the JCache API. This configuration
+ * creates the used cache for the application and enables statistics that become
+ * accessible via JMX.
+ */
+@Configuration
+@EnableCaching
+class CacheConfiguration {
+
+ @Bean
+ public JCacheManagerCustomizer petclinicCacheConfigurationCustomizer() {
+ return cm -> {
+ cm.createCache("vets", cacheConfiguration());
+ };
+ }
+
+ /**
+ * Create a simple configuration that enable statistics via the JCache programmatic
+ * configuration API.
+ *
+ * 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
+ * configuration options (like the size limit) must be set via a configuration
+ * mechanism that is provided by the selected JCache implementation.
+ */
+ private javax.cache.configuration.Configuration cacheConfiguration() {
+ return new MutableConfiguration<>().setStatisticsEnabled(true);
+ }
+}
\ No newline at end of file
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Owner.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Owner.java
index ac8fe4385..9619d9759 100644
--- a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Owner.java
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Owner.java
@@ -6,6 +6,8 @@
import lombok.Setter;
import javax.persistence.*;
+import javax.validation.constraints.Digits;
+import javax.validation.constraints.NotEmpty;
import java.util.HashSet;
import java.util.Set;
@@ -33,12 +35,16 @@ public Owner(Long id, String firstName, String lastName, String address, String
}
@Column(name = "address")
+ @NotEmpty
private String address;
@Column(name = "city")
+ @NotEmpty
private String city;
@Column(name = "telephone")
+ @NotEmpty
+ @Digits(fraction = 0, integer = 10)
private String telephone;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Person.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Person.java
index 2b684429a..fbd37c186 100644
--- a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Person.java
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Person.java
@@ -7,6 +7,7 @@
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
+import javax.validation.constraints.NotEmpty;
/**
* Created by jt on 7/13/18.
@@ -25,9 +26,11 @@ public Person(Long id, String firstName, String lastName) {
}
@Column(name = "first_name")
+ @NotEmpty
private String firstName;
@Column(name = "last_name")
+ @NotEmpty
private String lastName;
}
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Pet.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Pet.java
index dbfa259ec..c1c4e538f 100644
--- a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Pet.java
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Pet.java
@@ -4,6 +4,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
+import javax.validation.constraints.NotEmpty;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
@@ -33,6 +34,7 @@ public Pet(Long id, String name, PetType petType, Owner owner, LocalDate birthDa
}
@Column(name = "name")
+ @NotEmpty
private String name;
@ManyToOne
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Visit.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Visit.java
index 8517571f9..44478a737 100644
--- a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Visit.java
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/model/Visit.java
@@ -1,8 +1,10 @@
package guru.springframework.sfgpetclinic.model;
import lombok.*;
+import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
+import javax.validation.constraints.NotEmpty;
import java.time.LocalDate;
/**
@@ -18,9 +20,11 @@
public class Visit extends BaseEntity {
@Column(name = "date")
+ @DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
@Column(name = "description")
+ @NotEmpty
private String description;
@ManyToOne
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/repositories/VetRepository.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/repositories/VetRepository.java
index 0af0f91c2..2e21df2ab 100644
--- a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/repositories/VetRepository.java
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/repositories/VetRepository.java
@@ -1,10 +1,18 @@
package guru.springframework.sfgpetclinic.repositories;
import guru.springframework.sfgpetclinic.model.Vet;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.dao.DataAccessException;
import org.springframework.data.repository.CrudRepository;
+import javax.transaction.Transactional;
+import java.util.Collection;
+
/**
* Created by jt on 8/5/18.
*/
public interface VetRepository extends CrudRepository {
+ @Transactional
+ @Cacheable("vets")
+ Collection findAll() throws DataAccessException;
}
diff --git a/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/validators/PetValidator.java b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/validators/PetValidator.java
new file mode 100644
index 000000000..51c51337a
--- /dev/null
+++ b/pet-clinic-data/src/main/java/guru/springframework/sfgpetclinic/validators/PetValidator.java
@@ -0,0 +1,42 @@
+package guru.springframework.sfgpetclinic.validators;
+
+import guru.springframework.sfgpetclinic.model.Pet;
+import org.springframework.util.StringUtils;
+import org.springframework.validation.Errors;
+import org.springframework.validation.Validator;
+
+/**
+ * @author Gaetan Bloch
+ * Created on 30/03/2020
+ */
+public final class PetValidator implements Validator {
+ private static final String REQUIRED = "required";
+
+ @Override
+ public void validate(Object obj, Errors errors) {
+ Pet pet = (Pet) obj;
+ String name = pet.getName();
+ // name validation
+ if (!StringUtils.hasLength(name)) {
+ errors.rejectValue("name", REQUIRED, REQUIRED);
+ }
+
+ // type validation
+ if (pet.isNew() && pet.getPetType() == null) {
+ errors.rejectValue("petType", 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 Pet.class.isAssignableFrom(clazz);
+ }
+}
diff --git a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/CrashController.java b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/CrashController.java
new file mode 100644
index 000000000..b68c05040
--- /dev/null
+++ b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/CrashController.java
@@ -0,0 +1,18 @@
+package guru.springframework.sfgpetclinic.controllers;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+
+/**
+ * @author Gaetan Bloch
+ * Created on 30/03/2020
+ */
+@Controller
+final class CrashController {
+ static final String URL_OUPS = "/oups";
+
+ @GetMapping(URL_OUPS)
+ public String triggerException() {
+ throw new RuntimeException("Expected: controller used to showcase what happens when an exception is thrown");
+ }
+}
diff --git a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/IndexController.java b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/IndexController.java
index 020b271e0..eb1633a2e 100644
--- a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/IndexController.java
+++ b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/IndexController.java
@@ -14,9 +14,4 @@ public String index(){
return "index";
}
-
- @RequestMapping("/oups")
- public String oupsHandler(){
- return "notimplemented";
- }
}
diff --git a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/OwnerController.java b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/OwnerController.java
index 394432160..9f2deeb0d 100644
--- a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/OwnerController.java
+++ b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/OwnerController.java
@@ -18,7 +18,7 @@
@RequestMapping("/owners")
@Controller
public class OwnerController {
- private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
+ static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
private final OwnerService ownerService;
diff --git a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/PetController.java b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/PetController.java
index 5597cbaa3..226195a57 100644
--- a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/PetController.java
+++ b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/PetController.java
@@ -6,6 +6,7 @@
import guru.springframework.sfgpetclinic.services.OwnerService;
import guru.springframework.sfgpetclinic.services.PetService;
import guru.springframework.sfgpetclinic.services.PetTypeService;
+import guru.springframework.sfgpetclinic.validators.PetValidator;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
@@ -24,7 +25,7 @@
@RequestMapping("/owners/{ownerId}")
public class PetController {
- private static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
+ static final String VIEWS_PETS_CREATE_OR_UPDATE_FORM = "pets/createOrUpdatePetForm";
private final PetService petService;
private final OwnerService ownerService;
@@ -51,6 +52,11 @@ public void initOwnerBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("id");
}
+ @InitBinder("pet")
+ public void initPetBinder(WebDataBinder dataBinder) {
+ dataBinder.setValidator(new PetValidator());
+ }
+
@GetMapping("/pets/new")
public String initCreationForm(Owner owner, Model model) {
Pet pet = new Pet();
diff --git a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/VisitController.java b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/VisitController.java
index ca1394dde..d8480223c 100644
--- a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/VisitController.java
+++ b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/controllers/VisitController.java
@@ -21,6 +21,8 @@
@Controller
public class VisitController {
+ static final String PETS_CREATE_OR_UPDATE_VISIT_FORM = "pets/createOrUpdateVisitForm";
+
private final VisitService visitService;
private final PetService petService;
@@ -64,14 +66,14 @@ public Visit loadPetWithVisit(@PathVariable("petId") Long petId, Map model) {
- return "pets/createOrUpdateVisitForm";
+ return PETS_CREATE_OR_UPDATE_VISIT_FORM;
}
// Spring MVC calls method loadPetWithVisit(...) before processNewVisitForm is called
@PostMapping("/owners/{ownerId}/pets/{petId}/visits/new")
public String processNewVisitForm(@Valid Visit visit, BindingResult result) {
if (result.hasErrors()) {
- return "pets/createOrUpdateVisitForm";
+ return PETS_CREATE_OR_UPDATE_VISIT_FORM;
} else {
visitService.save(visit);
diff --git a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatter.java b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatter.java
index 43a2296ec..0db7165eb 100644
--- a/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatter.java
+++ b/pet-clinic-web/src/main/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatter.java
@@ -13,7 +13,7 @@
* Created by jt on 9/22/18.
*/
@Component
-public class PetTypeFormatter implements Formatter {
+public final class PetTypeFormatter implements Formatter {
private final PetTypeService petTypeService;
diff --git a/pet-clinic-web/src/main/resources/application.properties b/pet-clinic-web/src/main/resources/application.properties
index 4e700166c..f8dc89ac3 100644
--- a/pet-clinic-web/src/main/resources/application.properties
+++ b/pet-clinic-web/src/main/resources/application.properties
@@ -3,4 +3,7 @@ spring.banner.image.location=vizsla.jpg
# Internationalization
spring.messages.basename=messages/messages
-spring.profiles.active=springdatajpa
\ No newline at end of file
+spring.profiles.active=springdatajpa
+
+# Maximum time static resources should be cached
+spring.resources.cache.cachecontrol.max-age=12h
\ No newline at end of file
diff --git a/pet-clinic-web/src/main/resources/templates/error.html b/pet-clinic-web/src/main/resources/templates/error.html
new file mode 100644
index 000000000..b9026690e
--- /dev/null
+++ b/pet-clinic-web/src/main/resources/templates/error.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+ Something happened...
+ Exception message
+
+
+
\ No newline at end of file
diff --git a/pet-clinic-web/src/main/resources/templates/notimplemented.html b/pet-clinic-web/src/main/resources/templates/notimplemented.html
deleted file mode 100644
index 4eea12409..000000000
--- a/pet-clinic-web/src/main/resources/templates/notimplemented.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
- Not Implemented
-
-
- Not Implemented Yet!
-
-
\ No newline at end of file
diff --git a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/PetClinicIntegrationTest.java b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/PetClinicIntegrationTest.java
new file mode 100644
index 000000000..f678d47bc
--- /dev/null
+++ b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/PetClinicIntegrationTest.java
@@ -0,0 +1,19 @@
+package guru.springframework.sfgpetclinic;
+
+import guru.springframework.sfgpetclinic.services.VetService;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public class PetClinicIntegrationTest {
+
+ @Autowired
+ private VetService vetService;
+
+ @Test
+ void testFindAll() {
+ vetService.findAll();
+ vetService.findAll(); // served from cache
+ }
+}
diff --git a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/CrashControllerTest.java b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/CrashControllerTest.java
new file mode 100644
index 000000000..6f0b67cde
--- /dev/null
+++ b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/CrashControllerTest.java
@@ -0,0 +1,30 @@
+package guru.springframework.sfgpetclinic.controllers;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.util.NestedServletException;
+
+import static guru.springframework.sfgpetclinic.controllers.CrashController.URL_OUPS;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+
+/**
+ * @author Gaetan Bloch
+ * Created on 30/03/2020
+ */
+class CrashControllerTest {
+ private MockMvc mockMvc;
+
+ @BeforeEach
+ void setUp() {
+ mockMvc = MockMvcBuilders.standaloneSetup(new CrashController()).build();
+ }
+
+ @Test
+ void triggerExceptionTest() {
+ assertThrows(NestedServletException.class, () -> mockMvc.perform(get(URL_OUPS)));
+ }
+}
\ No newline at end of file
diff --git a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/OwnerControllerTest.java b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/OwnerControllerTest.java
index 267bb8b7a..8c3e5767d 100644
--- a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/OwnerControllerTest.java
+++ b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/OwnerControllerTest.java
@@ -5,18 +5,20 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import java.util.*;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import static guru.springframework.sfgpetclinic.controllers.OwnerController.VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
import static org.hamcrest.Matchers.*;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -25,6 +27,9 @@
@ExtendWith(MockitoExtension.class)
class OwnerControllerTest {
+ private static final String URL_OWNERS_NEW = "/owners/new";
+ private static final String URL_OWNERS_EDIT = "/owners/1/edit";
+
@Mock
OwnerService ownerService;
@@ -84,10 +89,11 @@ void processFindFormEmptyReturnMany() throws Exception {
Owner.builder().id(2l).build()));
mockMvc.perform(get("/owners")
- .param("lastName",""))
+ .param("lastName", ""))
.andExpect(status().isOk())
.andExpect(view().name("owners/ownersList"))
- .andExpect(model().attribute("selections", hasSize(2)));;
+ .andExpect(model().attribute("selections", hasSize(2)));
+ ;
}
@Test
@@ -103,47 +109,93 @@ void displayOwner() throws Exception {
@Test
void initCreationForm() throws Exception {
- mockMvc.perform(get("/owners/new"))
+ mockMvc.perform(get(URL_OWNERS_NEW))
.andExpect(status().isOk())
- .andExpect(view().name("owners/createOrUpdateOwnerForm"))
+ .andExpect(view().name(VIEWS_OWNER_CREATE_OR_UPDATE_FORM))
.andExpect(model().attributeExists("owner"));
verifyZeroInteractions(ownerService);
}
@Test
- void processCreationForm() throws Exception {
- when(ownerService.save(ArgumentMatchers.any())).thenReturn(Owner.builder().id(1l).build());
-
- mockMvc.perform(post("/owners/new"))
+ void processCreationFormTest() throws Exception {
+ // Given
+ when(ownerService.save(any())).thenReturn(Owner.builder().id(1L).build());
+
+ // When
+ mockMvc.perform(post(URL_OWNERS_NEW)
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("firstName", "John")
+ .param("lastName", "Doe")
+ .param("address", "123 Paris street")
+ .param("city", "Paris")
+ .param("telephone", "0123123123"))
+
+ // Then
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/1"))
.andExpect(model().attributeExists("owner"));
- verify(ownerService).save(ArgumentMatchers.any());
+ verify(ownerService).save(any());
+ }
+
+ @Test
+ void processCreationFormValidationFailedTest() throws Exception {
+ // When
+ mockMvc.perform(post(URL_OWNERS_NEW).contentType(MediaType.APPLICATION_FORM_URLENCODED))
+
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(view().name(VIEWS_OWNER_CREATE_OR_UPDATE_FORM))
+ .andExpect(model().attributeExists("owner"));
+
+ verifyZeroInteractions(ownerService);
}
@Test
void initUpdateOwnerForm() throws Exception {
when(ownerService.findById(anyLong())).thenReturn(Owner.builder().id(1l).build());
- mockMvc.perform(get("/owners/1/edit"))
+ mockMvc.perform(get(URL_OWNERS_EDIT))
.andExpect(status().isOk())
- .andExpect(view().name("owners/createOrUpdateOwnerForm"))
+ .andExpect(view().name(VIEWS_OWNER_CREATE_OR_UPDATE_FORM))
.andExpect(model().attributeExists("owner"));
verifyZeroInteractions(ownerService);
}
@Test
- void processUpdateOwnerForm() throws Exception {
- when(ownerService.save(ArgumentMatchers.any())).thenReturn(Owner.builder().id(1l).build());
-
- mockMvc.perform(post("/owners/1/edit"))
+ void processUpdateOwnerFormTest() throws Exception {
+ // Given
+ when(ownerService.save(any())).thenReturn(Owner.builder().id(1L).build());
+
+ // When
+ mockMvc.perform(post(URL_OWNERS_EDIT)
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("firstName", "John")
+ .param("lastName", "Doe")
+ .param("address", "123 Paris street")
+ .param("city", "Paris")
+ .param("telephone", "0123123123"))
+
+ // Then
.andExpect(status().is3xxRedirection())
.andExpect(view().name("redirect:/owners/1"))
.andExpect(model().attributeExists("owner"));
- verify(ownerService).save(ArgumentMatchers.any());
+ verify(ownerService).save(any());
+ }
+
+ @Test
+ void processUpdateOwnerFormValidationFailedTest() throws Exception {
+ // When
+ mockMvc.perform(post(URL_OWNERS_EDIT).contentType(MediaType.APPLICATION_FORM_URLENCODED))
+
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(view().name(VIEWS_OWNER_CREATE_OR_UPDATE_FORM))
+ .andExpect(model().attributeExists("owner"));
+
+ verifyZeroInteractions(ownerService);
}
}
\ No newline at end of file
diff --git a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/PetControllerTest.java b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/PetControllerTest.java
index d8076776b..38dc53470 100644
--- a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/PetControllerTest.java
+++ b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/PetControllerTest.java
@@ -1,5 +1,6 @@
package guru.springframework.sfgpetclinic.controllers;
+import guru.springframework.sfgpetclinic.formatters.PetTypeFormatter;
import guru.springframework.sfgpetclinic.model.Owner;
import guru.springframework.sfgpetclinic.model.Pet;
import guru.springframework.sfgpetclinic.model.PetType;
@@ -12,16 +13,18 @@
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.format.support.DefaultFormattingConversionService;
+import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import java.util.HashSet;
import java.util.Set;
+import static guru.springframework.sfgpetclinic.controllers.PetController.VIEWS_PETS_CREATE_OR_UPDATE_FORM;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -29,6 +32,9 @@
@ExtendWith(MockitoExtension.class)
class PetControllerTest {
+ private static final String URL_PETS_NEW = "/owners/1/pets/new";
+ private static final String URL_PETS_EDIT = "/owners/1/pets/2/edit";
+
@Mock
PetService petService;
@@ -54,8 +60,11 @@ void setUp() {
petTypes.add(PetType.builder().id(1L).name("Dog").build());
petTypes.add(PetType.builder().id(2L).name("Cat").build());
+ var conversionService = new DefaultFormattingConversionService();
+ conversionService.addFormatterForFieldType(PetType.class, new PetTypeFormatter(petTypeService));
mockMvc = MockMvcBuilders
.standaloneSetup(petController)
+ .setConversionService(conversionService)
.build();
}
@@ -64,50 +73,100 @@ void initCreationForm() throws Exception {
when(ownerService.findById(anyLong())).thenReturn(owner);
when(petTypeService.findAll()).thenReturn(petTypes);
- mockMvc.perform(get("/owners/1/pets/new"))
+ mockMvc.perform(get(URL_PETS_NEW))
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(model().attributeExists("pet"))
- .andExpect(view().name("pets/createOrUpdatePetForm"));
+ .andExpect(view().name(VIEWS_PETS_CREATE_OR_UPDATE_FORM));
}
@Test
- void processCreationForm() throws Exception {
+ void processCreationFormTest() throws Exception {
+ // Given
when(ownerService.findById(anyLong())).thenReturn(owner);
when(petTypeService.findAll()).thenReturn(petTypes);
- mockMvc.perform(post("/owners/1/pets/new"))
+ // When
+ mockMvc.perform(post(URL_PETS_NEW)
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("name", "Milou")
+ .param("birthDate", "2020-01-01")
+ .param("petType", "Dog"))
+
+ // Then
.andExpect(status().is3xxRedirection())
- .andExpect(view().name("redirect:/owners/1"));
+ .andExpect(view().name("redirect:/owners/1"))
+ .andExpect(model().attributeExists("owner"))
+ .andExpect(model().attributeExists("pet"));
verify(petService).save(any());
}
+ @Test
+ void processCreationFormValidationFailedTest() throws Exception {
+ // Given
+ when(ownerService.findById(anyLong())).thenReturn(owner);
+
+ // When
+ mockMvc.perform(post(URL_PETS_NEW).contentType(MediaType.APPLICATION_FORM_URLENCODED))
+
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(view().name(VIEWS_PETS_CREATE_OR_UPDATE_FORM))
+ .andExpect(model().attributeExists("owner"))
+ .andExpect(model().attributeExists("pet"));
+
+ verifyZeroInteractions(petService);
+ }
+
@Test
void initUpdateForm() throws Exception {
when(ownerService.findById(anyLong())).thenReturn(owner);
when(petTypeService.findAll()).thenReturn(petTypes);
when(petService.findById(anyLong())).thenReturn(Pet.builder().id(2L).build());
- mockMvc.perform(get("/owners/1/pets/2/edit"))
+ mockMvc.perform(get(URL_PETS_EDIT))
.andExpect(status().isOk())
.andExpect(model().attributeExists("owner"))
.andExpect(model().attributeExists("pet"))
- .andExpect(view().name("pets/createOrUpdatePetForm"));
+ .andExpect(view().name(VIEWS_PETS_CREATE_OR_UPDATE_FORM));
}
@Test
- void processUpdateForm() throws Exception {
+ void processUpdateFormTest() throws Exception {
+ // Given
when(ownerService.findById(anyLong())).thenReturn(owner);
when(petTypeService.findAll()).thenReturn(petTypes);
- mockMvc.perform(post("/owners/1/pets/2/edit"))
+ // When
+ mockMvc.perform(post(URL_PETS_EDIT)
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("name", "Caline")
+ .param("birthDate", "2020-01-01")
+ .param("petType", "Cat"))
+
+ // Then
.andExpect(status().is3xxRedirection())
- .andExpect(view().name("redirect:/owners/1"));
+ .andExpect(view().name("redirect:/owners/1"))
+ .andExpect(model().attributeExists("pet"))
+ .andExpect(model().attributeExists("owner"));
verify(petService).save(any());
}
+ @Test
+ void processUpdateFormValidationFailedTest() throws Exception {
+ // When
+ mockMvc.perform(post(URL_PETS_EDIT).contentType(MediaType.APPLICATION_FORM_URLENCODED))
+
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(view().name(VIEWS_PETS_CREATE_OR_UPDATE_FORM))
+ .andExpect(model().attributeExists("pet"));
+
+ verifyZeroInteractions(petService);
+ }
+
@Test
void populatePetTypes() {
//todo impl
diff --git a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/VisitControllerTest.java b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/VisitControllerTest.java
index 41542c328..ea011c83b 100644
--- a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/VisitControllerTest.java
+++ b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/controllers/VisitControllerTest.java
@@ -22,6 +22,7 @@
import java.util.HashSet;
import java.util.Map;
+import static guru.springframework.sfgpetclinic.controllers.VisitController.PETS_CREATE_OR_UPDATE_VISIT_FORM;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -31,7 +32,6 @@
@ExtendWith(MockitoExtension.class)
class VisitControllerTest {
- private static final String PETS_CREATE_OR_UPDATE_VISIT_FORM = "pets/createOrUpdateVisitForm";
private static final String REDIRECT_OWNERS_1 = "redirect:/owners/{ownerId}";
private static final String YET_ANOTHER_VISIT_DESCRIPTION = "yet another visit";
@@ -57,18 +57,18 @@ void setUp() {
when(petService.findById(anyLong()))
.thenReturn(
Pet.builder()
- .id(petId)
- .birthDate(LocalDate.of(2018,11,13))
- .name("Cutie")
- .visits(new HashSet<>())
- .owner(Owner.builder()
- .id(ownerId)
- .lastName("Doe")
- .firstName("Joe")
- .build())
- .petType(PetType.builder()
- .name("Dog").build())
- .build()
+ .id(petId)
+ .birthDate(LocalDate.of(2018, 11, 13))
+ .name("Cutie")
+ .visits(new HashSet<>())
+ .owner(Owner.builder()
+ .id(ownerId)
+ .lastName("Doe")
+ .firstName("Joe")
+ .build())
+ .petType(PetType.builder()
+ .name("Dog").build())
+ .build()
);
uriVariables.clear();
@@ -91,14 +91,29 @@ void initNewVisitForm() throws Exception {
@Test
- void processNewVisitForm() throws Exception {
+ void processNewVisitFormTest() throws Exception {
+ // When
mockMvc.perform(post(visitsUri)
- .contentType(MediaType.APPLICATION_FORM_URLENCODED)
- .param("date","2018-11-11")
- .param("description", YET_ANOTHER_VISIT_DESCRIPTION))
+ .contentType(MediaType.APPLICATION_FORM_URLENCODED)
+ .param("date", "2018-11-11")
+ .param("description", YET_ANOTHER_VISIT_DESCRIPTION))
+
+ // Then
.andExpect(status().is3xxRedirection())
.andExpect(view().name(REDIRECT_OWNERS_1))
.andExpect(model().attributeExists("visit"))
- ;
+ .andExpect(model().attributeExists("pet"));
+ }
+
+ @Test
+ void processNewVisitFormValidationFailedTest() throws Exception {
+ // When
+ mockMvc.perform(post(visitsUri).contentType(MediaType.APPLICATION_FORM_URLENCODED))
+
+ // Then
+ .andExpect(status().isOk())
+ .andExpect(view().name(PETS_CREATE_OR_UPDATE_VISIT_FORM))
+ .andExpect(model().attributeExists("visit"))
+ .andExpect(model().attributeExists("pet"));
}
}
\ No newline at end of file
diff --git a/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatterTest.java b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatterTest.java
new file mode 100644
index 000000000..4af6ba444
--- /dev/null
+++ b/pet-clinic-web/src/test/java/guru/springframework/sfgpetclinic/formatters/PetTypeFormatterTest.java
@@ -0,0 +1,74 @@
+package guru.springframework.sfgpetclinic.formatters;
+
+import guru.springframework.sfgpetclinic.model.PetType;
+import guru.springframework.sfgpetclinic.services.PetTypeService;
+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 java.text.ParseException;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author Gaetan Bloch
+ * Created on 30/03/2020
+ */
+@ExtendWith(MockitoExtension.class)
+class PetTypeFormatterTest {
+
+ private static final String PET_TYPE_DOG = "Dog";
+ private static final String PET_TYPE_CAT = "Cat";
+ private static final String PET_TYPE_FISH = "Fish";
+
+ private PetTypeFormatter petTypeFormatter;
+
+ private Set petTypes;
+
+ @Mock
+ private PetTypeService petTypeService;
+
+ @BeforeEach
+ void setup() {
+ petTypeFormatter = new PetTypeFormatter(petTypeService);
+ petTypes = new HashSet<>();
+ petTypes.add(PetType.builder().id(1L).name(PET_TYPE_DOG).build());
+ petTypes.add(PetType.builder().id(2L).name(PET_TYPE_CAT).build());
+ }
+
+ @Test
+ void printTest() {
+ // Given
+ PetType petType = PetType.builder().name(PET_TYPE_DOG).build();
+
+ // When
+ String petTypeName = petTypeFormatter.print(petType, Locale.ENGLISH);
+
+ // Then
+ assertEquals(PET_TYPE_DOG, petTypeName);
+ }
+
+ @Test
+ void parseTest() throws ParseException {
+ // Given
+ when(petTypeService.findAll()).thenReturn(petTypes);
+
+ // When
+ PetType petType = petTypeFormatter.parse(PET_TYPE_CAT, Locale.ENGLISH);
+
+ // Then
+ assertEquals(PET_TYPE_CAT, petType.getName());
+ }
+
+ @Test
+ void parseThrowsExceptionTest() {
+ Assertions.assertThrows(ParseException.class, () -> petTypeFormatter.parse(PET_TYPE_FISH, Locale.ENGLISH));
+ }
+}
\ No newline at end of file