등록 기능 만들기
- PostsApiController를 web 패키지에
- PostsSaveRequestDto를 web.dto 패키지에
- PostsService를 service.posts 패키지에
PostsApiController
package com.danny.makewebalone.web;
import com.danny.makewebalone.service.posts.PostsService;
import com.danny.makewebalone.web.dto.PostsSaveRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RequiredArgsConstructor
@RestController
public class PostsApiController {
private final PostsService postsService;
@PostMapping("/api/v1/posts")
public Long save(@RequestBody PostsSaveRequestDto requestDto) {
return postsService.save(requestDto);
}
}
PostsService
package com.danny.makewebalone.service.posts;
import com.danny.makewebalone.web.domain.posts.PostsRepository;
import com.danny.makewebalone.web.dto.PostsSaveRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class PostsService {
private final PostsRepository postsRepository;
@Transactional
public Long save(PostsSaveRequestDto requestDto) {
return postsRepository.save(requestDto.toEntity()).getId();
}
}
Controller와 Service에 @Autowired가 없다?
스프링에서 Bean을 주입받는 방식
- @Autowired
- setter
- 생성자
이중 가장 권장하는 방식은 생성자로 주입받는 방식
@Autowired는 권장하지 않음
생성자로 Bean 객체를 받도록 하면 @Autowired와 동일한 효괄를 볼 수 있다.
그렇다면 생성자는 어디에?
@RequiredArgsConstructor에서 해결
final이 선언된 모든 필드를 인자값으로 하는 생성자
생성자를 직접 안 쓰고 롬복 어노테이션을 사용한 이유는 해당 클래스의 의존성 관계가 변경될 때마다 생성자 코드를 계속해서 수정하는 번거로움을 해결하기 위함
PostsSaveRequestDto
package com.danny.makewebalone.web.dto;
import com.danny.makewebalone.web.domain.posts.Posts;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
private String title;
private String content;
private String author;
@Builder
public PostsSaveRequestDto(String title, String content, String author) {
this.title = title;
this.content = content;
this.author = author;
}
public Posts toEntity() {
return Posts.builder()
.title(title)
.content(content)
.author(author)
.build();
}
}
Entity 클래스와 거의 유사한 형태임에도 Dto를 추가로 생성한 이유
- Entity 클래스는 Request/Response 클래스로 사용하면 안됨
- Entity 클래스는 데이터베이스와 맞닿은 핵심 클래스
- Entity 클래스를 기준으로 테이블이 생성되고, 스키마가 변경됨
- 화면 변경은 아주 사소한 변경인데 이를 위해 테이블과 연결된 Entity 클래스를 변경하는 것은 너무 큰 변경
- 수많은 서비스 클래스나 비즈니스 로직들이 Entity 클래스를 기준으로 동작
- Request와 Response용 Dto는 View를 위한 클래스라 정말 자주 변경이 필요
- Controller에서 결과값으로 여러 테이블을 조인해서 줘야 할 경우가 빈번하므로 Entity클래스만으로 표현하기가 어려운 경우가 많으니 꼭 Entity클래스와 Controller에서 쓸 Dto는 분리해서 사용할 것
PostsApiControllerTest 생성하기
package com.danny.makewebalone.web;
import com.danny.makewebalone.web.domain.posts.Posts;
import com.danny.makewebalone.web.domain.posts.PostsRepository;
import com.danny.makewebalone.web.dto.PostsSaveRequestDto;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private PostsRepository postsRepository;
private MockMvc mvc;
@After
public void tearDown() throws Exception {
postsRepository.deleteAll();
}
@Test
public void Posts_등록된다() throws Exception {
//given
String title = "title";
String content = "content";
PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
.title(title)
.content(content)
.author("author")
.build();
String url = "http://localhost:" + port + "/api/v1/posts";
//when
ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);
//then
assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(responseEntity.getBody()).isGreaterThan(0L);
List<Posts> all = postsRepository.findAll();
assertThat(all.get(0).getTitle()).isEqualTo(title);
assertThat(all.get(0).getContent()).isEqualTo(content);
}
}
Api Controller를 테스트하는데 HelloController와 달리 @WebMvcTest를 사용하지 않는 이유
- @WebMvcTest의 경우 JPA 기능이 작동하지 않기 때문
- Controller와 ControllerAdvice 등 외부 연동과 관련된 부분만 활성화 됨
- 지금과 같이 JPA 기능까지 한번에 테스트할 때는 @SpringBootTest와 TestRestTemplate 사용
Test 결과
Passed
'스프링 부트와 AWS' 카테고리의 다른 글
03장. 스프링 부트에서 JPA로 데이터베이스 다뤄보자05 (0) | 2022.11.14 |
---|---|
03장. 스프링 부트에서 JPA로 데이터베이스 다뤄보자04 (0) | 2022.11.14 |
03장. 스프링 부트에서 JPA로 데이터베이스 다뤄보자02 (0) | 2022.11.11 |
03장. 스프링 부트에서 JPA로 데이터베이스 다뤄보자01 (0) | 2022.11.11 |
02장. 스프링 부트에서 테스트 코드를 작성하자 (0) | 2022.11.11 |
댓글