Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- baeldung
- Kernighan의 C언어 프로그래밍
- resttemplate
- 페이징
- 네트워크 설정
- 이터레이터
- 친절한SQL튜닝
- 티스토리 쿠키 삭제
- 자바편
- 코드로배우는스프링부트웹프로젝트
- 스프링부트핵심가이드
- 코드로배우는스프링웹프로젝트
- /etc/network/interfaces
- 목록처리
- 처음 만나는 AI 수학 with Python
- 스프링 시큐리티
- 선형대수
- iterator
- 자료구조와함께배우는알고리즘입문
- 서버설정
- 리눅스
- 구멍가게코딩단
- 처음 만나는 AI수학 with Python
- 데비안
- 알파회계
- network configuration
- GIT
- 자료구조와 함께 배우는 알고리즘 입문
- ㅒ
- d
Archives
- Today
- Total
bright jazz music
blog04: 작성글 저장1 - 게시글 저장 구현 본문
포스트 저장을 위한 클래스 생성과 설정
- service 패키지 생성: PostService.java 생성
- repository 패키지 생성: PostRepository.java 인터페이스 생성( JPARepository 인터페이스 구현(여기서는 extends). JPARepository 인터페이스의 제네릭 파라미터로는 Post 엔티티와 Long형식의 PK 전달)
- domain 패키지 생성:
- Post 클래스 생성(@Entity 애노테이션 부착)
기본적인 흐름
- Json 데이터 ==> PostController(PostCreate 타입으로 바인딩) ==> PostService(일반 클래스인 PostCreate타입을 엔티티로 변환) ==> PostRepository 호출해서 save
//PostController.java
@Slf4j
@RestController
@RequiredArgsConstructor
public class PostController {
private final PostService postService;
// 생성자를 통한 주입. 여기서는 롬복 @RequiredArgsConstructor를 사용한다.
// public PostController(PostService postService){
// this.postService = postService;
// }
@PostMapping("/posts")
public Map<String, String> post(@RequestBody @Valid PostCreate request) {
postService.write(request);
return Map.of();
}
}
//PostService.java
package com.endofma.blog.service;
import com.endofma.blog.domain.Post;
import com.endofma.blog.repository.PostRepository;
import com.endofma.blog.request.PostCreate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class PostService {
// @Autowired // 필드 인젝션을 사용하는 것은 바람직하지 않다. 따라서 생성자를 사용해서 생성해준다.
// private PostRepository postRepository;
private final PostRepository postRepository;
// /생성자를 사용한 필드 인젝션을 사용하면 생성자가 새로 생긴다. 그러나 롬복의 @RequiredArgsConstructor를 사용할 수도 있다.
// public PostService(PostRepository postRepository) {
// this.postRepository = postRepository;
// }
public void write(PostCreate postCreate) {
// postRepository.save(postCreate); 이렇게는 들어가지 않는다. postCreate는 dto 형태이지 엔티티가 아니기 때문이다.
// 따라서 일반 클래스인 postCreate를 엔티티 형태로 변환해 줘야 한다
Post post = new Post(postCreate.getTitle(), postCreate.getContent());
// post.title = postCreate.getTitle(); Post의 접근 제한자를 public으로 바꿔서 이렇게 하는 것은 좋지 않다. 값이 변경될 수 있다.
// 따라서 필드의 접근 제한자를 private으로 바꾼 뒤 생성자를 통해 생성하도록 만드는 것이 좋다.
postRepository.save(post);
}
}
//Post.java : 엔티티
package com.endofma.blog.domain;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PUBLIC) //엔티티는 기본 생성자가 필요하다.
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
@Lob //DB에서 큰 크기의 파일을 저장할 때 사용하는 타입(Large Object). 자바에서 긴 스트링 값으로 넘어가는 값을 DB에서 저장할 수 있도록 사용.
private String content;
public Post(String title, String content){
this.title = title;
this.content = content;
}
}
//PostRepository.java
package com.endofma.blog.repository;
import com.endofma.blog.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> { //Post 엔티티와 primary key 형식이 들어간다.
}
//PostControllerTest.java
//@WebMvcTest //간단한 웹테스트만 가능. 우린 스프링 전반에 걸쳐 여러 가지를 만들었기 때문에 @SpringBootTest가 필요
@AutoConfigureMockMvc //@WebMvcTest가 없어지면 기존 테스트가 안되므로 @WebMvcTest를 구성하는 애노테이션을 떼내어 붙였다.
@SpringBootTest
class PostControllerTest {
@Autowired
private MockMvc mockMvc; ////Could not autowire. No beans of 'MockMvc' type found.
@Autowired
private PostRepository postRepository; //DB저장 테스트를 위해 주입
@BeforeEach
void clean() {
postRepository.deleteAll();
}
@Test
@DisplayName("/posts 요청시 hello world를 출력한다")
void test() throws Exception {
//expected
//기본적으로 Content-Type을 application/json으로 보냄. 예전에는 application/x-www-form-urlencoded를 썼다.
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON) //기본값이라 주석처리
.content("{\"title\": \"제목입니다.\", \"content\": \"내용입니다.\"}") //json 형태로 값 넣어주기
)
.andExpect(status().isOk()) //http response 가 200인지
.andExpect(content().string("{}")) // 내용이 hello world인지
.andDo(print()); //요청에 대한 전반적인 요약을 출력해준다.
}
//PostControllerTest.java
@Test
@DisplayName("/posts 요청시 title 값은 필수다")
void test2() throws Exception {
//expected
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\": null, \"content\": \"내용입니다.\"}") //title 값을 빈 스트링으로 설정하였다.
// .content("{\"title\": \"안녕\", \"content\": \"내용입니다.\"}") //title 값을 빈 스트링으로 설정하였다.
)
.andExpect(status().isBadRequest()) //.OK()
.andExpect(jsonPath("$.code").value("400")) //json 검증
.andExpect(jsonPath("$.message").value("잘못된 요청입니다!"))
// .andExpect(jsonPath("$.validation.title").value("타이틀을 입력하세요!"))
.andDo(print());
}
@Test
@DisplayName("/posts 요청시 DB에 값이 저장된다.")
void test3() throws Exception {
//when
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\": \"제목입니다.\", \"content\": \"내용입니다.\"}") //title 값을 빈 스트링으로 설정하였다.
)
.andExpect(status().isOk())
.andDo(print());
//then
assertEquals(1L, postRepository.count()); //하나의 값이 있을 거라고 예상. 일치
//DB에 잘 들어갔는지 확인
Post post = postRepository.findAll().get(0); //가장 처음 데이터 가져옴
assertEquals("제목입니다.", post.getTitle());
assertEquals("내용입니다.", post.getContent());
}
}
결과
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.6)
2022-12-28 10:30:48.027 INFO 22288 --- [ Test worker] c.e.blog.controller.PostControllerTest : Starting PostControllerTest using Java 11.0.12 on DESKTOP-8H1PTVG with PID 22288 (started by markany-hjcha in D:\personal\blog)
2022-12-28 10:30:48.028 INFO 22288 --- [ Test worker] c.e.blog.controller.PostControllerTest : No active profile set, falling back to 1 default profile: "default"
2022-12-28 10:30:48.593 INFO 22288 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-12-28 10:30:48.636 INFO 22288 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 36 ms. Found 1 JPA repository interfaces.
2022-12-28 10:30:49.057 INFO 22288 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-12-28 10:30:49.252 INFO 22288 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-12-28 10:30:49.319 INFO 22288 --- [ Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-12-28 10:30:49.377 INFO 22288 --- [ Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.14.Final
2022-12-28 10:30:49.564 INFO 22288 --- [ Test worker] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-12-28 10:30:49.741 INFO 22288 --- [ Test worker] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-12-28 10:30:50.360 INFO 22288 --- [ Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-12-28 10:30:50.368 INFO 22288 --- [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-12-28 10:30:50.852 WARN 22288 --- [ Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2022-12-28 10:30:51.377 INFO 22288 --- [ Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2022-12-28 10:30:51.377 INFO 22288 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2022-12-28 10:30:51.378 INFO 22288 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2022-12-28 10:30:51.397 INFO 22288 --- [ Test worker] c.e.blog.controller.PostControllerTest : Started PostControllerTest in 3.678 seconds (JVM running for 5.993)
MockHttpServletRequest:
HTTP Method = POST
Request URI = /posts
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"60"]
Body = {"title": "제목입니다.", "content": "내용입니다."}
Session Attrs = {}
Handler:
Type = com.endofma.blog.controller.PostController
Method = com.endofma.blog.controller.PostController#post(PostCreate)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json"]
Content type = application/json
Body = {}
Forwarded URL = null
Redirected URL = null
Cookies = []
2022-12-28 10:30:51.896 INFO 22288 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2022-12-28 10:30:51.896 INFO 22288 --- [ionShutdownHook] .SchemaDropperImpl$DelayedDropActionImpl : HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
2022-12-28 10:30:51.902 INFO 22288 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-12-28 10:30:51.905 INFO 22288 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 7s
4 actionable tasks: 2 executed, 2 up-to-date
AM 10:30:52: Execution finished ':test --tests "com.endofma.blog.controller.PostControllerTest.test3"'.
개별 테스트가 서로에게 영향을 주지 않도록 처리
그런데 개별 테스트 메소드가 아니라 클래스 전체 테스트를 돌렸을 때는 에러가 발생한다. 왜냐하면 test1()에서 이미 한 번 데이터를 넣고 test3()에서 한 번 더 넣기 때문에 저장된 값이 2개가 되어 기대값이 1개와 달라지기 때문이다.
별 문제가 아닌 것처럼 보일 수 있지만 이는 나중에 작성되는 모든 테스트 케이스에 영향을 미치기 때문에 테스트 클래스 전체를 테스트 할 때 문제가 될 수 있다.
개별 테스트를 하기 전에 저장공간에서 데이터를 삭제함으로써 영향을 제거할 수 있다. 각 테스트 메소드마다 삭제 문구를 넣어주는 것이다. 이는 코드가 지저분해 지고 개발자가 신경을 써줘야 하는 단점이 있다.
이는 @BeforeEach를 사용해서 해결할 수 있다. @BeforeEach는 각각의 메소드를 실행하기 전에 특정 구문을 실행해 준다. 아래는 예시다.
@AutoConfigureMockMvc
@SpringBootTest
class PostControllerTest {
@Autowired
private MockMvc mockMvc; ////Could not autowire. No beans of 'MockMvc' type found.
@Autowired
private PostRepository postRepository; //DB저장 테스트를 위해 주입
@BeforeEach
void clean() {
postRepository.deleteAll();
}
//...
}
//이렇게 구문을 추가하고 클래스 전체 테스트를 실행하면 오류가 발생하지 않는다.
'Projects > blog' 카테고리의 다른 글
blog05: 단건조회 (포스트 조회) (0) | 2022.12.29 |
---|---|
blog04: 작성글 저장2 - ObjectMapper(jackson)사용, 클래스 분리 (0) | 2022.12.28 |
blog03: 데이터 검증3 (ExceptionController) (0) | 2022.12.27 |
blog03: 데이터 검증2 (ExceptionController, @ControllerAdvice) (0) | 2022.12.13 |
blog03: 데이터 검증1 (@NotBlank, @Valid, BindingResult) (0) | 2022.12.12 |
Comments