기본 콘텐츠로 건너뛰기

냉장고 가계부 프로젝트 19

Fridge 프로젝트를 열고 수정, 삭제, 전체삭제 기능을 구현한다. 먼저 Repository 테스트 클래스부터 작성한다. @RunWith(SpringRunner.class) @DataJpaTest public class JpaFridgeRepositoryTests { @Autowired JpaFridgeRepository jpaFridgeRepository; @Test public void createFridge() { String nickname = "myFridge"; Fridge fridge = jpaFridgeRepository.save(new Fridge(nickname)); assertThat(fridge).isNotNull(); assertThat(fridge.getNickname()).isEqualTo(nickname); assertThat(fridge.getNickname()).isNotEqualTo("another nickname"); } @Test public void update() { Fridge fridge = jpaFridgeRepository.save(new Fridge("myFridge")); assertThat(jpaFridgeRepository.findOne(fridge.getId())).isNotNull(); Fridge changeNicknameFridge = new Fridge("otherFridge"); changeNicknameFridge.setId(fridge.getId()); jpaFridgeRepository.save(changeNicknameFridge); Fridge sa...

냉장고 가계부 프로젝트 18

fridge-web 프로젝트에서 FridgeControllerTests 클래스를 리팩토링한다. public class FridgeControllerTests extends ControllerBase { @Override protected void setUp() { } @Test public void findCreateFridgeButtonAndClick() { browser.get(BASE_URL + "/fridges"); WebElement createFridgeBtn = browser.findElementById("btnCreateFridge"); assertThat(createFridgeBtn).isNotNull(); createFridgeBtn.click(); assertThat(browser.getCurrentUrl()).isEqualTo(BASE_URL + "/fridges/add"); } @Test public void fillInCreateFormAndSubmit() { browser.get(BASE_URL + "/fridges/add"); String nickname = "myFridge"; WebElement nicknameElement = browser.findElement(By.name("nickname")); nicknameElement.sendKeys(nickname); browser.findElementByTagName("form").submit(); WebDriverWait wait = new WebDriv...

냉장고 가계부 프로젝트 17

fridge 프로젝트에서 FoodController 메서드들을 리팩토링하고, 전체삭제기능을 추가한다. FoodControllerTests 클래스부터 작성한다. @Test public void deleteAll() throws Exception { doNothing().when(jpaFoodService).removeAll(); URI uri = UriComponentsBuilder.fromUriString("/foods").build().toUri(); mvc.perform(MockMvcRequestBuilders.delete(uri)) .andExpect(status().isNoContent()) .andExpect(content().string("")); verify(jpaFoodService, times(1)).removeAll(); } deleteAll 테스트 메서드는 전체삭제기능을 테스트한다. jpaFoodService Mock 객체에 removeAll 메서드를 호출하면 void 를 리턴하기때문에 doNothing mockito 메서드를 호출한다. /foods URL을 DELETE 방식으로 호출하면 HttpStatus는 NoContent를 응답하고 ResponseBody 는 비어있다. mock객체를 검증하면 jpaFoodService 의 removeAll 메서드가 1번 호출했음을 확인한다. 다음은 FoodController 구현부이다. @DeleteMapping public ResponseEntity<?> deleteAllFood() { jpaFoodService.removeAll(); return ResponseEntity.noContent().build(); } @DeleteMapping 애너테이션을 선언하면 de...

냉장고 가계부 프로젝트 16

Fridge UI를 구현하기위해 fridge-web 프로젝트를 작업한다. 여기에도 앞에 글에서 정리한것과 같이 패키지 정리가 안되어 있으므로 패키지를 정리한다. food 패키지와 fridge 패키지로 나누고 각 클래스들을 옮긴다. 테스트 클래스들도 마찬가지로 패키지를 구분한다. Fridge 를 표현하는 FridgeController 를 작성하고 테스트 클래스를 만든다. FridgeControllerTests 클래스는 Selenium으로 웹UI를 테스트하는 테스트 클래스이다. 이미 FoodControllerTests 클래스에서 selenium API를 사용하는 부분이 존재하고 FridgeController 에서도 별 차이없을 것이므로 selenium WebDriver 할당부분등의 중복되는 부분은 추상클래스(ControllerBase)로 옮기고 FoodControllerTests와 FridgeControllerTests 클래스에서 상속받는 구조로 작성한다. 다음은 ControllerBase 클래스 소스코드이다. @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT) public abstract class ControllerBase { protected static ChromeDriver browser; @Autowired protected RestTemplate restTemplate; @Value("${local.server.port}") private int port; private String host = "http://localhost"; protected static String BASE_URL; @Before public void setUp() { BASE_URL = host + ...

냉장고 가계부 프로젝트 15

Fridge 클래스와 매핑되는 DB 테이블을 작성한다. resources 클래스패스에 있는 schema.sql 파일을 다음과 같이 편집한다. CREATE TABLE fridge ( id INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1, INCREMENT BY 1) PRIMARY KEY, nickname VARCHAR(45) NOT NULL ); id 컬럼은 INTEGER 타입으로 Primary Key, AutoIncrement 속성을 가진다. nickname은 문자열 타입을 가진다. Fridge 클래스의 Jpa 테스트 클래스를 작성한다. 클래스명은 JpaFridgeRepositoryTests으로 정한다. @RunWith(SpringRunner.class) @DataJpaTest public class JpaFridgeRepositoryTests { @Autowired JpaFridgeRepository jpaFridgeRepository; @Test public void createFridge() { String nickname = "myFridge"; Fridge fridge = jpaFridgeRepository.save(new Fridge(nickname)); assertThat(fridge).isNotNull(); assertThat(fridge.getNickname()).isEqualTo(nickname); assertThat(fridge.getNickname()).isNotEqualTo("another nickname"); } } JpaFoodRepositoryTests 클래스와 유사하다. createFridge 테스트 메서드는 간단하게 새로운 fridge를 만들어낸다. Fridge 클래스와 매핑할 수 있도록 J...

냉장고 가계부 프로젝트 14

fridge-web 프로젝트에서 api 서버와 통신할때 URL을 매번 중복해서 입력하는 부분을 제거하기 위해 RestTemplate 빈 등록메서드를 수정한다. @Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder.rootUri("http://localhost:8081").build(); } builder에 rootUri 메서드를 호출해서 api 서버 url을 미리 설정하고 build해서 RestTemplate 객체를 반환하면 RestTemplate을 사용하는 부분에서는 root 다음 경로만 넘겨주면 된다. WebFoodController 클래스의 restTemplate 사용부분을 전부 수정한다. @GetMapping public String foods(Model model) { ResponseEntity<Resources<FoodCommand>> response = restTemplate.exchange("/foods", HttpMethod.GET, null, new ParameterizedTypeReference<Resources<FoodCommand>>() {}, Collections.emptyMap()); .... 다른 메서드들도 동일하게 수정한다. Food 클래스에서도 이제 더이상 사용하지 않는 Cloneable과 hashCode, equals 메서드를 정리한다. Guava 의존라이브러리도 다른곳에서는 사용하지 않으므로 정리한다. 식품에 대한 최소한의 기능은 구현하였다. 이번에는 냉장고 라는 개념을 모델링한다. 식품들이 들어가고 나가는 곳은 냉장고이기 때문에 냉장고라는...

냉장고 가계부 프로젝트 13

foods.html 파일에 식품 리스트 UI를 구현한다. <body> <h2>Foods</h2> <table> <thead> <tr> <th>식품명</th> <th>수량</th> <th>유통기한</th> </tr> </thead> <tbody> <tr th:each="food : ${foodList}"> <td><a href="/web/foods/1" th:href="@{/web/foods/{id}(id=${food.id})}" th:text="${food.name}">파스퇴르 우유 1.8L</a></td> <td th:text="${food.quantity}">1</td> <td th:text="${#dates.format(food.expiryDate, 'yyyy-MM-dd')}">2018-09-28</td> </tr> </tbody> </table> </body> 기존 h2 태그의 hello world 테스트 문구는 Foods 로 변경하고, table 태그를 작성하여 목록을 보여준다. 상세보기 앵커태그와 수량, 유통기한 값을 'yyyy-MM-dd' 패턴으로 포맷한다. pom.xml 에 HATEOAS 라이브러리 의존성을 추가한다. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-star...