10. Spring/JUnit5

01. JUnit 5 Test - MockMvc, Mockito, JPA [미완성]

THE HEYDAZE 2020. 8. 7. 20:02
OS Windows 10 Home 64bit 버전 1903 (OS 빌드 18362.836)
FrameWork SpringBoot 2.3.1.RELEASE
EditTool IntelliJ Idea 2019.1.3
BuildTool Gradle
TestFrameWork JUnit 5

# 설정

Gradle dependency 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Spring
implementation 'org.springframework.boot:spring-boot-starter-web'
// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// H2 DataBase
runtimeOnly 'com.h2database:h2'
// JUnit 5
testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
 
cs

 

application.yml 설정

1
2
3
4
5
6
7
8
9
10
11
12
spring:
  h2:
    console:
      enabled: true
 
 
  datasource:
    url: jdbc:h2:mem:default
 
  jpa:
    show-sql: true
 
cs

 

# 파일

JUnit.zip
0.01MB

 

# JUnit4 와 JUnit5
JUnit 4 (junit.vintage) JUnit 5 (junit.jupiter)
@RunWith @ExtendWith
@Before @BeforeEach
@After @AfterEach

 

 

 

 

# 테스트 코드 구조
  간단 구조   설명
  given   객체를 생성 할 때 사용
  when   객체를 얻어 올 때 사용
  then   객체를 비교 할 때 사용
  verify   확인 작업을 할 때 사용

 

# AssertJ
  메소드   설명
  isEqualTo(obj)   obj 와 같다
  isNotEqualTo(obj)   obj 와  다르다
  isEqualToIgnoringCase(str)   str 와 같다  (대소문자 무시)
  contains(str)   str 를 포함한다
  containsIgnoringCase(str)   str 를 포함한다 (대소문자 무시)
  doesNotContain(str)   str 를 포함하지 않는다
  startsWith(str)   str 로 시작한다
  doesNotStartWith(str)   str 로 시작하지 않는다
  endsWith(str)   str 로 끝난다
  doesNotEndWith(str)   str 로 끝나지 않는다
  matches(regex)   regex 정규식과 같다
  doesNotMatch(regex)   regex 정규식과 같지 않다
  isLowerCase(str)   str 은 소문자로 이루어져 있다
  isUpperCase(str)   str 은 대문자로 이루어져 있다
  isZero(n)   n 은 0 이다
  isNotZero(n)   n 은  0 이 아니다
  isOne(n)   n 은  1 이다
  isPositive(n)   n 은  양수 이다
  isNegative(n)   n 은  음수 이다
  isNotPositive(n)   n 은  양수 또는 0 이다
  isBetween(start, end)   start 와 end 사이의 값 이다
  isStrictlyBetween(start, end)   start 와 end 사이의 값이 아니다
  isCloseTo(n, within 또는 offset)   주어진 within 또는 offset 에 가까운 값이다.
  isNotCloseTo(n, byLessThan 또는 offset)   주어진 within 또는 offset 에 가까운 값이 아니다
  isCloseTo(n, withinPercentage)   주어진 백분율 내에서 주어진 숫자에 가깝다
  isNotCloseTo(n, withinPercentage)   주어진 백분율 내에서 주어진 숫자에 가깝지 않다
  isTrue()   참이다
  isFalse()   거짓이다
  isNull()   null 값 이다
  isNotNull()   null 값 아니다
  isBlank()   빈 값 이다 (공백 미포함)
  isNotBlank()   빈 값 아니다 (공백 미포함)
  isEmpty()   빈 값 이다 (공백 포함)
  isNotEmpty()   빈 값 아니다 (공백 미포함)
  isNullOrEmpty()   null 값 이거나 빈 값 이다 (공백 포함)
  isLessThan(str)   str 보다 낮은 문자열 이다 (Ascii 코드)
  isIn(...obj)   여러개의 obj 중 1개와 같다
  isNotIn(...obj)   여러개의 obj 와 모두 다르다
  filteredOn(...)   list 필터
  extracting(...)   list 프로퍼티 값

 

 

#1. Controller TEST ( MockMvc 이용 )
유효성 검사 및 컨트롤 동작 여부를 위한 테스트 (입력값 잘못 입력)

 

  TEST static method   Controller @RequestMapping
  org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get   @GetMapping
  org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post   @PostMapping
  org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put   @PutMapping
  org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch   @PatchMapping
  org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete   @DeleteMapping

 

패키지 메소드 설명
org.springframework.test.web.servlet.request   contentType()   전송 타입 (주로 json 형태)
  content()   전송 내용 (주로 json 형태)
  header()   전송 헤더 (주로 Authorization)
  headers()   전송 여러개의 헤더
  param()   전송 파리미터 (name, value 형태로 전송)
  params()   전송 여러개의 파라미터
  queryParam()   전송 쿼리형식 파라미터 (name=a&age=1)
  queryParams()   전송 여러개의 쿼리형식 파라미터
  cookie()   전송 쿠키
  locale()   전송 로컬영역
  requestAttr()   전송 request scope속성
  sessionAttr()   전송 session scope 속성
  flashAttr()   전송 falsh scope 속성
  session()   전송 세션
  principal()   전송 principal
  contextPath()   프로젝트 명
  servletPath()   패키지 + 파일명
  characterEncoding()   인코더 (UTF-8)
org.springframework.test.web.servlet.result
(static 메소드)
  status()   httpStatus 검증 (200, 201, 404, 500...) 
  header()   header 검증
  content()   content 검증 ( body 영역 )
  jsonPath()   json 형식으로 content 검증
  xpath()   xpath 형식으로 content 검증
  cookie()   cookie 검증
  view   컨트롤러가 반환한 뷰 이름 검증
  forwardedUrl   이동대상의 경로를 검증
  forwardedUrlPattern    이동대상의 경로를 패턴으로 검증
  redirectedUrl   리다이렉트 대상의 경로 또는 URL 검증
  redirectedUrlPattern    리타이렉트 대상의 경로 또는 URL 패턴 검증
  model   스프링 MVC 모델 상태 검증
  request   비동기 처리의 상태나 요청 스코프 상태 검증

ServletPath & ContextPath & URL 

더보기
[출처] https://m.blog.naver.com/PostView.nhn?blogId=ssongiy&logNo=110161636470&proxyReferer=https:%2F%2Fwww.google.com%2F

 

#1 Mokito 를 이용한 MockMvc TEST

[방법 1]

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@SpringBootTest
class MemberControllerTests {
 
    private MockMvc mvc;
 
    @MockBean
    private MemberService memberService;
 
    @Autowired
    private ObjectMapper objectMapper;
 
    Member mockMember = Member.builder()
                        .memberId("martin")
                        .password("1234")
                        .build();
 
    List<Member> mockMembers = new ArrayList<>();
 
    @BeforeEach
    void setUp(@Autowired MemberController memberController) {
        // MockMvc
        mvc = MockMvcBuilders.standaloneSetup(memberController).build();
 
        // MockMembers
        mockMembers.add(mockMember);
        mockMembers.add(Member.builder().memberId("benny").password("1111").build());
        mockMembers.add(Member.builder().memberId("tony").password("2222").build());
    }
 
    public String toJsonString(Member member) throws JsonProcessingException {
        return objectMapper.writeValueAsString(member);
    }
 
    @Test
    @DisplayName("아이디 생성")
    void create() throws Exception {
        // given
        given(memberService.create(any())).will(invocation -> {
            Member member = invocation.getArgument(0);
            member.setId(1L);
            return member;
        }); // willReturn 방식을 이용하면, id 는 null 값
 
        // when
        final ResultActions actions = mvc.perform(post("/v1/members")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJsonString(mockMember)));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isCreated())
                .andExpect(header().string("location""/members/1"))
                .andExpect(content().string(containsString("martin")));
    }
 
    @Test
    @DisplayName("아이디 길이 제한 초과")
    void createWithMemberIdInvalidData() throws Exception {
 
        // given
        Member invalidData = Member.builder()
                .memberId("abcdefghijklmnopqrstu"// 21
                .password("1234")
                .build();
 
        given(memberService.create(any())).willReturn(invalidData);
 
        //when
        mvc.perform(post("/v1/members")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJsonString(invalidData)))
                .andDo(print())
                .andExpect(status().isBadRequest()); // @Valid, @Size-max=20
    }
 
    @Test
    @DisplayName("멤버 조회")
    void detail() throws Exception {
        // given
        given(memberService.detail(any())).willReturn(mockMember);
 
        // when
        final ResultActions actions = mvc.perform(get("/v1/members/1"));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.memberId").value("martin"))
                .andExpect(jsonPath("$.password").value("1234"));
    }
 
    @Test
    @DisplayName("멤버 리스트")
    void list() throws Exception {
        // given
        given(memberService.list()).willReturn(mockMembers);
 
        // when
        final ResultActions actions = mvc.perform(get("/v1/members"));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.[0].memberId").value("martin"))
                .andExpect(jsonPath("$.[0].password").value("1234"))
                .andExpect(jsonPath("$.[1].memberId").value("benny"))
                .andExpect(jsonPath("$.[1].password").value("1111"))
                .andExpect(jsonPath("$.[2].memberId").value("tony"))
                .andExpect(jsonPath("$.[2].password").value("2222"));
    }
 
    @Test
    @DisplayName("멤버 수정")
    void update() throws Exception {
 
        // request
        Member updateMember = Member.updateBuilder()
                .point(10000L)
                .build();
 
 
        // when
        final ResultActions actions = mvc.perform(patch("/v1/members/1")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(toJsonString(updateMember)));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk());
 
 
        // verify
        verify(memberService).update(eq(updateMember), eq(1L));
 
    }
 
    @Test
    @DisplayName("멤버 삭제")
    void del() throws Exception {
 
        // when
        final ResultActions actions = mvc.perform(delete("/v1/members/1"));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk());
 
        // verify
        verify(memberService).delete(eq(1L));
 
    }
}
 
cs

 

[방법 2]

 

코드 그림
ObjectMapper

@BeforeEach
@MockBean
@DisplayName

given

any()

get()

print()

status()

jsonPath()
verify()

참고 : @Test void delete() {...} 할 경우 perform(delete(...)) 의 static 메소드 delete 과 충돌하여 del 로 하였습니다

 

리스트의 경우 사이즈를 확인하고자 할 때

jsonPath("$.*", hasSize(5))

 


[기타 참고]

스프링 버전이 상위 버전인 경우 

mockMvc 의 andDo(print()) 메소드에서, MockHttpServletResponse 에 한글이 깨지는 문제가 있는 데

이럴 때는 컨트롤러 매핑에 produces 를 설정해주면 된다

 

 

[spring] Deprecate MediaType.APPLICATION_JSON_UTF8 in favor of APPLICATION_JSON - I'm honeymon(JiHeon Kim).

 

honeymon.io

 

#2 SQL 을 이용한 MockMvc TEST
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@SpringBootTest
@Transactional
class MemberControllerTests2 {
 
    private MockMvc mvc;
 
    @Autowired
    private MemberRepository memberRepository;
 
    @Autowired
    private ObjectMapper objectMapper;
 
    @BeforeEach
    void setUp(@Autowired MemberController memberController) {
        // MockMvc
        mvc = MockMvcBuilders.standaloneSetup(memberController).build();
    }
 
    public String toJsonString(Member member) throws JsonProcessingException {
        return objectMapper.writeValueAsString(member);
    }
 
    @Test
    @DisplayName("아이디 생성")
    void create() throws Exception {
 
        // request
        Member member = Member.allBuilder()
                .memberId("martin")
                .password("1234")
                .point(100L)
                .build();
 
        // when
        final ResultActions actions = mvc.perform(post("/v1/members")
            .contentType(MediaType.APPLICATION_JSON)
            .content(toJsonString(member)));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isCreated());
 
        // verify
        assertThat(memberRepository.findById(1L).get().getMemberId()).isEqualTo("martin");
        assertThat(memberRepository.findById(1L).get().getPassword()).isEqualTo("1234");
        assertThat(memberRepository.findById(1L).get().getPoint()).isEqualTo(100L);
 
    }
 
    @Test
    @DisplayName("아이디 길이 제한 초과")
    void createWithMemberIdInvalidData() throws Exception {
        // request
        Member invalidData = Member.builder()
                .memberId("abcdefghijklmnopqrstu"// 21
                .password("1234")
                .build();
 
        System.out.println(invalidData);
 
        // when
        mvc.perform(post("/v1/members")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJsonString(invalidData)))
                .andDo(print())
                .andExpect(status().isBadRequest());
 
    }
 
    @Test
    @DisplayName("중복된 아이디")
    @Sql("/detail.sql")
    void createWithMemberIdExistedData() throws Exception {
        // request
        Member existedData = Member.allBuilder()
                .memberId("martin")
                .password("1234")
                .point(100L)
                .build();
 
        // when
        NestedServletException e = assertThrows(NestedServletException.class, () ->
                mvc.perform(post("/v1/members")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(toJsonString(existedData)))
        );
 
        // then
        assertThat(e.getMessage()).contains("MemberIdExistedException");
 
    }
 
 
    @Test
    @DisplayName("멤버 조회")
    @Sql("/detail.sql")
    void detail() throws Exception {
 
        // when
        final ResultActions actions = mvc.perform(get("/v1/members/1"));
 
        // then
        actions
            .andDo(print())
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.memberId").value("martin"))
            .andExpect(jsonPath("$.password").value("1234"));
    }
 
    @Test
    @DisplayName("멤버 리스트")
    @Sql("/list.sql")
    void list() throws Exception {
 
        // when
        final ResultActions actions = mvc.perform(get("/v1/members"));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.[0].memberId").value("martin"))
                .andExpect(jsonPath("$.[0].password").value("1234"))
                .andExpect(jsonPath("$.[1].memberId").value("benny"))
                .andExpect(jsonPath("$.[1].password").value("1111"))
                .andExpect(jsonPath("$.[2].memberId").value("tony"))
                .andExpect(jsonPath("$.[2].password").value("2222"));
    }
 
    @Test
    @DisplayName("멤버 수정")
    @Sql("/detail.sql")
    void update() throws Exception {
 
        // request
        Member updateMember = Member.updateBuilder()
                .point(10000L)
                .build();
 
        System.out.println(updateMember);
 
        // when
        final ResultActions actions = mvc.perform(patch("/v1/members/1")
                .contentType(MediaType.APPLICATION_JSON)
                .content(toJsonString(updateMember)));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk());
 
        // verify
        assertThat(memberRepository.findById(1L).get().getPoint()).isEqualTo(10000L);
 
    }
 
    @Test
    @DisplayName("멤버 삭제")
    @Sql("/detail.sql")
    void del() throws Exception {
 
        // when
        final ResultActions actions = mvc.perform(delete("/v1/members/1"));
 
        // then
        actions
                .andDo(print())
                .andExpect(status().isOk());
 
        // verify
        assertThat(memberRepository.existsById(1L)).isFalse();
    }
}
 
cs

update 와 delete 의 경우 Controller 에서 void 로 설정하여 반환이 없기 때문에 확인하기 위해

repository 를 이용하였다.

 

@Transactional 을 사용해야 @Sql 의 쿼리문 사용가능하며, @Test 코드가 끝나면 rollback 을 한다

(DB 를 이용한 작업은 DB에 INSERT, UPDATE, CREATE 의 DML 작업이 반영이 되기 때문에 

@Transactional 코드를 사용하지 않는 경우 일일이 지워야한다.)

 

@Sql 을 이용하여 지정하거나, data.sql 를 생성하여 지정 ( data source sql default 가 resources/data.sql 이기 때문 )

각 메소드마다 @Sql 를 사용하는 방법은 단위 테스트에는 적합하나

통합 테스트에는 다른 단위 테스트와 충돌 할 수 있어 통합 테스트는 적절하지 않을 수 있다.

 

#2-1 Service TEST ( SpringBootTest 이용)

[ Account ]

@Entity
@NoArgsConstructor
@Getter
@ToString
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @CreationTimestamp
    private Date createAt;

    private Account(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public static Account of(String username, String password) {
        return new Account(username, password);
    }
}

 

[ AccountPayload ]

@Getter
@Setter
@ToString
public class AccountPayload {

    private String username;

    private String password;

    public Account toEntity(String passwordEncoder) {
        return Account.of(username, passwordEncoder);
    }
}

 

[ AccountRepository ]

public interface AccountRepository extends JpaRepository<Account, Long> {
    
    Optional<Account> findByUsername(String username);

    Boolean existsByUsername(String username);
}

 

[ SignInService ]

@Service
@RequiredArgsConstructor
public class SignInService {

    private final AccountRepository accountRepository;
    private final PasswordEncoder passwordEncoder;

    public Account signIn(AccountPayload accountPayload) {

        Account account = accountRepository.findByUsername(accountPayload.getUsername()).orElseThrow(() -> new IllegalArgumentException("아이디가 존재하지 않습니다"));

        if (! passwordEncoder.matches(accountPayload.getPassword(), account.getPassword()) ) {
            throw new IllegalArgumentException("비밀번호가 일치하지 않습니다");
        }

        return account;
    }
}

 

[ SignInServiceTest ]

@SpringBootTest
class SignInServiceTest {

    @Autowired
    private SignUpService signUpService;

    @Test
    @DisplayName("가입과 아이디 중복가입 방지")
    void signUp() {

        // given
        String username = "root";
        String password = "root";
        AccountPayload accountPayload = new AccountPayload();
        accountPayload.setUsername(username);
        accountPayload.setPassword(password);

        // when
        signUpService.signUp(accountPayload);

        // then
        assertThat(accountPayload.getUsername()).isEqualTo("root");

        // when & then
        assertThatThrownBy(() -> signUpService.signUp(accountPayload))
                .isInstanceOf(IllegalArgumentException.class);
    }
}

assertThatThrownBy 를 이용하여 exception 을 테스트 할 수 있다.

 

+ 추가 Page 테스트

[ AccountFindService ]

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true) // JPA 조회에서 성능 개선을 위해 readOnly = true 사용
public class AccountFindService {

    private final AccountRepository accountRepository;

    public Page<Account> findPage(Pageable pageable) {

        return accountRepository.findAll(pageable);
    }

    public List<Account> findAll() {
        return accountRepository.findAll();
    }
}

 

[ AccountFindServiceTest ]

@SpringBootTest
@TestConstructor(autowireMode = TestConstructor.AutowireMode.ALL) // SpringBoot 2.2 에서 추가됨 - 기존 @Autowired 방식 X
class AccountFindServiceTest {

    private final AccountFindService accountFindService;
    private final SignUpService signUpService;

    AccountFindServiceTest(AccountFindService accountFindService, SignUpService signUpService) {
        this.accountFindService = accountFindService;
        this.signUpService = signUpService;
    }

    @Test
    void pageTest() {

        init();

        // given
        int page = 0;
        int size = 2;
        PageRequest pageRequest = PageRequest.of(page, size);

        // when
        Page<Account> accounts = accountFindService.findPage(pageRequest);

        // then
        System.out.println("accounts.getTotalElements : " + accounts.getTotalElements());
        System.out.println("accounts.getTotalPages : " + accounts.getTotalPages());
        System.out.println("accounts.getSize : " + accounts.getSize());
        System.out.println("accounts.getContent : " + accounts.getContent());

        accounts.forEach(System.out::println);
    }

    private void init() {
        AccountPayload martin = new AccountPayload();
        martin.setUsername("martin");
        martin.setPassword("root");

        AccountPayload benny = new AccountPayload();
        benny.setUsername("benny");
        benny.setPassword("root");

        AccountPayload tony = new AccountPayload();
        tony.setUsername("tony");
        tony.setPassword("root");

        signUpService.signUp(martin);
        signUpService.signUp(benny);
        signUpService.signUp(tony);

    }
}

 

#2-2. Service TEST ( Mockito 이용 )
비지니스 로직에 대한 테스트 (아이디 중복, 포인트 부족)

 

Mockito(가짜객체) 를 이용하여 테스트 하는 이유

실제환경에서는 복잡한 데이터들이 서로 얽혀 있기 때문에 그 에 맞춰서 데이터를 집어넣기 번거러움

또한 외부 api (결제) 같은 경우 실제로 금액이 빠져나가기 때문에 실제 구동환경으로 테스트 할 수는 없다.

그래서 해당 메소드에서 테스트에 맞는 원하는 객체를 반환하고자 할 때 유용하다

 

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
class MemberServiceTests {
 
    private MemberService memberService;
 
    @Mock
    private MemberRepository memberRepository;
 
 
    @BeforeEach
    void setUp() {
        MockitoAnnotations.initMocks(this);
 
        memberService = new MemberService(memberRepository);
    }
 
 
    @Test
    @DisplayName("생성")
    void create() {
 
        Member mockMember = Member.builder()
                .memberId("martin")
                .password("1234")
                .build();
 
        given(memberRepository.save(any())).willReturn(mockMember);
 
        Member member = memberService.create(mockMember);
 
        assertThat(member.getMemberId()).isEqualTo("martin");
        assertThat(member.getPassword()).isEqualTo("1234");
    }
 
    @Test
    @DisplayName("아이디 중복")
    void createWithMemberIdExistedData() {
 
        Member mockMember = Member.builder()
                .memberId("martin")
                .password("1234")
                .build();
 
        // 아이디 중복
        given(memberRepository.existsByMemberId(any())).willReturn(true);
 
        assertThrows(MemberIdExistedException.class, () -> memberService.create(mockMember));
    }
 
    @Test
    @DisplayName("멤버 조회")
    void detail() {
        Member mockMember = Member.builder()
                .memberId("martin")
                .password("1234")
                .build();
 
        given(memberRepository.findById(1L)).willReturn(Optional.of(mockMember));
 
        Member member = memberService.detail(1L);
 
        assertThat(member.getMemberId()).isEqualTo("martin");
        assertThat(member.getPassword()).isEqualTo("1234");
 
    }
 
    @Test
    @DisplayName("멤버 리스트")
    void list() {
        List<Member> mockMembers = new ArrayList<>();
        mockMembers.add(Member.builder().memberId("martin").password("1234").build());
        mockMembers.add(Member.builder().memberId("denny").password("1111").build());
        mockMembers.add(Member.builder().memberId("tony").password("2222").build());
 
        given(memberRepository.findAll()).willReturn(mockMembers);
 
        List<Member> members = memberService.list();
 
        assertThat(members.get(0).getMemberId()).isEqualTo("martin");
        assertThat(members.get(0).getPassword()).isEqualTo("1234");
        assertThat(members.get(1).getMemberId()).isEqualTo("denny");
        assertThat(members.get(1).getPassword()).isEqualTo("1111");
        assertThat(members.get(2).getMemberId()).isEqualTo("tony");
        assertThat(members.get(2).getPassword()).isEqualTo("2222");
    }
 
    @Test
    @DisplayName("멤버 수정")
    void update() {
 
        Member updateMember = Member.updateBuilder()
                .point(100L)
                .build();
 
        Member mockMember = Member.allBuilder()
                .id(1L)
                .memberId("martin")
                .password("1234")
                .build();
 
        given(memberRepository.findById(1L)).willReturn(Optional.of(mockMember));
 
        memberService.update(updateMember, 1L);
 
        verify(memberRepository).save(eq(mockMember));
 
        assertThat(mockMember.getPoint()).isEqualTo(100L);
 
    }
 
    @Test
    @DisplayName("멤버 삭제")
    void del() {
        memberService.delete(1L);
 
        verify(memberRepository).deleteById(eq(1L));
    }
}
 
cs

 

@Mock 과 @MockBean
  org.mockito.@Mock   - Spring Boot Container 가 필요없음
  - Bean 이 container 에 존재 X
  - Mockito 내장
  org.springframework.boot.test.mock.mockito.@MockBean   - Spring Boot Container 가 필요함
  - Bean 이 container 에 존재 해야함
  - Spring 내장 

 

@ExtendWith 를 이용한 @Mock 사용방법 initMock 메소드를 이용한 @Mock 사용방법
  @Mock 사용법은 2개의 방법 중 1개의 방법만 사용하면 된다.

 

첫 번째 @InjectMocks 을 이용한 @Mock 주입 방법 두 번째 @BeforeEach 를 이용한 @Mock 주입 방법

 

  static 메소드   설명
  org.mockito.given()   가짜 객체를 생성할 때 사용
  org.mockito.when()   가짜 객체를 얻어올 때 사용
  org.mockito.then()   가짜 객체를 비교할 때 사용
  org.mockito.verify()   가짜 객체의 메소드를 실행했는 지 확인할 때 사용
   
  org.mockito.any()   어떠한 값
  org.mockito.isA()   ?
  org.mockito.anyBoolean()   어떠한 Boolean 값
  org.mockito.anyByte()   어떠한 Byte 값
  org.mockito.anyChar()   어떠한 Char 값
  org.mockito.anyInt()   어떠한 Int 값
  org.mockito.anyLong()   어떠한 Long 값
  org.mockito.anyDouble()   어떠한 Double 값
  org.mockito.anyFloat()   어떠한 Float 값
  org.mockito.anyString()   어떠한 String 값
  org.mockito.anyList()   어떠한 List 값
  org.mockito.anySet()   어떠한 Set 값
  org.mockito.anyMap()   어떠한 Map 값
  org.mockito.eq()   같은 값
  org.mockito.same()   ?
  org.mockito.isNull()   ?
  org.mockito.isNotNull()   ?
  org.mockito.contains()   ?
  org.mockito.matches()   ?
  org.mockito.startsWith()   ~ 시작하는 값
  org.mockito.endsWith()   ~ 끝나는 값
  org.mockito.never()   실행을 안했는 지 검증 (verify 와 같이 사용)
  org.mockito.times(n)   n 만큼 실행됬는 지 검증 (verfify 와 같이 사용)

 

 

#3. Repository TEST ( DataJpaTest 이용)
JPA가 정상적으로 실행되는지에 대한 테스트

 

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@DataJpaTest
@Sql("/list.sql")
class MemberRepositoryTests {
 
    @Autowired
    private MemberRepository memberRepository;
 
 
    @Test
    void findById() {
 
        // when
        Member member = memberRepository.findById(1L).get();
 
        // then
        assertThat(member.getMemberId()).isEqualTo("martin");
        assertThat(member.getPassword()).isEqualTo("1234");
 
        System.out.println(member);
    }
 
    @Test
    void findByAll() {
 
        // when
        List<Member> members = memberRepository.findAll();
 
        assertThat(members.get(0).getMemberId()).isEqualTo("martin");
        assertThat(members.get(0).getPassword()).isEqualTo("1234");
        assertThat(members.get(1).getMemberId()).isEqualTo("benny");
        assertThat(members.get(1).getPassword()).isEqualTo("1111");
        assertThat(members.get(2).getMemberId()).isEqualTo("tony");
        assertThat(members.get(2).getPassword()).isEqualTo("2222");
 
        members.forEach(System.out::println);
    }
 
    @Test
    void existsByMemberId() {
 
        Boolean existed = memberRepository.existsByMemberId("martin");
 
        assertThat(existed).isTrue();
 
    }
 
    @Test
    void del() {
 
        memberRepository.deleteById(1L);
 
        assertThat(memberRepository.existsById(1L)).isFalse();
    }
}
 
cs

 

# [참고] 통합테스트 최적화 방법

[ 문제 ]

1. Spring Test Code 설정에 대한 단일화

- A 테스트 클래스, B 테스트 클래스, C 테스트 클래스 모두 @SpringBootTest 등 어노테이션을 작성해야한다

2. Spring Bean Context N번 올라가는 문제

- 1번과 같은 반복적인 @SpringBootTest 문제로 인하여 SpringContextBean N번 올라가 속도저하를 발생

3. 데이터베이스 결합 테스트를 진행하는 경우 Repository를 지속적으로 DI 받아야 하는 문제

- 반복적인 @Autowired private XXXRepoistory xxxRepository 코드 사용

 

[ 통합테스트 방법 ]

통합테스트 JUnit 생성

(참고 Gradle 빌드 툴에서는 Gradle execute 에서 Gradle:test 를 하면 된다고 함 - 안되면 위 그림처럼 하시면 됩니다)

 

추상 클래스 생성

 

추상클래스를 상속받은 테스트 클래스

 

[ 결과 ]

SpringContextBean 이 1개만 올라오는 모습 (1개만 올라와야 정상)

 

3. 데이터베이스 결합 테스트를 진행하는 경우 Repository를 지속적으로 DI 받아야 하는 문제는 아래 사이트 참고바람

 

JPA 기반 테스트 코드 작성 팁 - Yun Blog | 기술 블로그

JPA 기반 테스트 코드 작성 팁 - Yun Blog | 기술 블로그

cheese10yun.github.io

 

#4. RestTemplate TEST

참고

테스트용 API
 

영화진흥위원회 오픈API

 

www.kobis.or.kr

  메소드   설명
  xxxForObject   http method 인 xxx 를 통해서 객체를 반환
  xxxForEntity   http method 인 xxx 를 통해 ResponseEntity 를 반환

 

GET 요청

더보기
{
  "boxOfficeResult": {
    "boxofficeType": "일별 박스오피스",
    "showRange": "20120101~20120101",
    "dailyBoxOfficeList": [
      {
        "rnum": "1",
        "rank": "1",
        "rankInten": "0",
        "rankOldAndNew": "OLD",
        "movieCd": "20112207",
        "movieNm": "미션임파서블:고스트프로토콜",
        "openDt": "2011-12-15",
        "salesAmt": "2776060500",
        "salesShare": "36.3",
        "salesInten": "-415699000",
        "salesChange": "-13",
        "salesAcc": "40541108500",
        "audiCnt": "353274",
        "audiInten": "-60106",
        "audiChange": "-14.5",
        "audiAcc": "5328435",
        "scrnCnt": "697",
        "showCnt": "3223"
      },
      {
        "rnum": "2",
        "rank": "2",
        "rankInten": "1",
        "rankOldAndNew": "OLD",
        "movieCd": "20110295",
        "movieNm": "마이 웨이",
        "openDt": "2011-12-21",
        "salesAmt": "1189058500",
        "salesShare": "15.6",
        "salesInten": "-105894500",
        "salesChange": "-8.2",
        "salesAcc": "13002897500",
        "audiCnt": "153501",
        "audiInten": "-16465",
        "audiChange": "-9.7",
        "audiAcc": "1739543",
        "scrnCnt": "588",
        "showCnt": "2321"
      },
      {
        "rnum": "3",
        "rank": "3",
        "rankInten": "-1",
        "rankOldAndNew": "OLD",
        "movieCd": "20112621",
        "movieNm": "셜록홈즈 : 그림자 게임",
        "openDt": "2011-12-21",
        "salesAmt": "1176022500",
        "salesShare": "15.4",
        "salesInten": "-210328500",
        "salesChange": "-15.2",
        "salesAcc": "10678327500",
        "audiCnt": "153004",
        "audiInten": "-31283",
        "audiChange": "-17",
        "audiAcc": "1442861",
        "scrnCnt": "360",
        "showCnt": "1832"
      },
      {
        "rnum": "4",
        "rank": "4",
        "rankInten": "0",
        "rankOldAndNew": "OLD",
        "movieCd": "20113260",
        "movieNm": "퍼펙트 게임",
        "openDt": "2011-12-21",
        "salesAmt": "644532000",
        "salesShare": "8.4",
        "salesInten": "-75116500",
        "salesChange": "-10.4",
        "salesAcc": "6640940000",
        "audiCnt": "83644",
        "audiInten": "-12225",
        "audiChange": "-12.8",
        "audiAcc": "895416",
        "scrnCnt": "396",
        "showCnt": "1364"
      },
      {
        "rnum": "5",
        "rank": "5",
        "rankInten": "0",
        "rankOldAndNew": "OLD",
        "movieCd": "20113271",
        "movieNm": "프렌즈: 몬스터섬의비밀 ",
        "openDt": "2011-12-29",
        "salesAmt": "436753500",
        "salesShare": "5.7",
        "salesInten": "-89051000",
        "salesChange": "-16.9",
        "salesAcc": "1523037000",
        "audiCnt": "55092",
        "audiInten": "-15568",
        "audiChange": "-22",
        "audiAcc": "202909",
        "scrnCnt": "290",
        "showCnt": "838"
      },
      {
        "rnum": "6",
        "rank": "6",
        "rankInten": "1",
        "rankOldAndNew": "OLD",
        "movieCd": "19940256",
        "movieNm": "라이온 킹",
        "openDt": "1994-07-02",
        "salesAmt": "507115500",
        "salesShare": "6.6",
        "salesInten": "-114593500",
        "salesChange": "-18.4",
        "salesAcc": "1841625000",
        "audiCnt": "45750",
        "audiInten": "-11699",
        "audiChange": "-20.4",
        "audiAcc": "171285",
        "scrnCnt": "244",
        "showCnt": "895"
      },
      {
        "rnum": "7",
        "rank": "7",
        "rankInten": "-1",
        "rankOldAndNew": "OLD",
        "movieCd": "20113381",
        "movieNm": "오싹한 연애",
        "openDt": "2011-12-01",
        "salesAmt": "344871000",
        "salesShare": "4.5",
        "salesInten": "-107005500",
        "salesChange": "-23.7",
        "salesAcc": "20634684500",
        "audiCnt": "45062",
        "audiInten": "-15926",
        "audiChange": "-26.1",
        "audiAcc": "2823060",
        "scrnCnt": "243",
        "showCnt": "839"
      },
      {
        "rnum": "8",
        "rank": "8",
        "rankInten": "0",
        "rankOldAndNew": "OLD",
        "movieCd": "20112709",
        "movieNm": "극장판 포켓몬스터 베스트 위시「비크티니와 백의 영웅 레시라무」",
        "openDt": "2011-12-22",
        "salesAmt": "167809500",
        "salesShare": "2.2",
        "salesInten": "-45900500",
        "salesChange": "-21.5",
        "salesAcc": "1897120000",
        "audiCnt": "24202",
        "audiInten": "-7756",
        "audiChange": "-24.3",
        "audiAcc": "285959",
        "scrnCnt": "186",
        "showCnt": "348"
      },
      {
        "rnum": "9",
        "rank": "9",
        "rankInten": "0",
        "rankOldAndNew": "OLD",
        "movieCd": "20113311",
        "movieNm": "앨빈과 슈퍼밴드3",
        "openDt": "2011-12-15",
        "salesAmt": "137030000",
        "salesShare": "1.8",
        "salesInten": "-35408000",
        "salesChange": "-20.5",
        "salesAcc": "3416675000",
        "audiCnt": "19729",
        "audiInten": "-6461",
        "audiChange": "-24.7",
        "audiAcc": "516289",
        "scrnCnt": "169",
        "showCnt": "359"
      },
      {
        "rnum": "10",
        "rank": "10",
        "rankInten": "0",
        "rankOldAndNew": "OLD",
        "movieCd": "20112708",
        "movieNm": "극장판 포켓몬스터 베스트 위시 「비크티니와 흑의 영웅 제크로무」",
        "openDt": "2011-12-22",
        "salesAmt": "125535500",
        "salesShare": "1.6",
        "salesInten": "-40756000",
        "salesChange": "-24.5",
        "salesAcc": "1595695000",
        "audiCnt": "17817",
        "audiInten": "-6554",
        "audiChange": "-26.9",
        "audiAcc": "235070",
        "scrnCnt": "175",
        "showCnt": "291"
      }
    ]
  }
}

 

[외부 API Get - getForEntity] Api 조회
class ApiTest {

    private RestTemplate restTemplate;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
    }

    @Test
    void getForEntity() {

        UriComponentsBuilder comp = UriComponentsBuilder.newInstance()
                .scheme("http").host("www.kobis.or.kr")
                .path("kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
                .queryParam("key", "430156241533f1d058c603178cc3ca0e")
                .queryParam("targetDt", "20120101");

        ResponseEntity<String> response = restTemplate.getForEntity(comp.toUriString(), String.class);
        
        System.out.println(response.getBody());
    }
}

결과

 

[외부 API Get - getForEntity] boxofficeType 가져오기
class ApiTest {

    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
        objectMapper = new ObjectMapper();
    }

    @Test
    void getForEntity() throws JsonProcessingException {

        UriComponentsBuilder comp = UriComponentsBuilder.newInstance()
                .scheme("http").host("www.kobis.or.kr")
                .path("kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
                .queryParam("key", "430156241533f1d058c603178cc3ca0e")
                .queryParam("targetDt", "20120101");

        ResponseEntity<String> response = restTemplate.getForEntity(comp.toUriString(), String.class);

        System.out.println(response.getBody());

        JsonNode jsonNode = objectMapper.readTree(Objects.requireNonNull(response.getBody()));

        JsonNode getBoxofficeTypeNode = jsonNode
                                            .get("boxOfficeResult")
                                            .get("boxofficeType");

        String boxofficeType = getBoxofficeTypeNode.asText();

        System.out.println("boxofficeType = " + boxofficeType);
    }
}

결과

 

[외부 API Get - getForEntity] dailyBoxOfficeList 의 movieNm 가져오기 - JsonNode.findValueAsText
class ApiTest {

    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
        objectMapper = new ObjectMapper();
    }

    @Test
    void getForEntity() throws JsonProcessingException {

        UriComponentsBuilder comp = UriComponentsBuilder.newInstance()
                .scheme("http").host("www.kobis.or.kr")
                .path("kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
                .queryParam("key", "430156241533f1d058c603178cc3ca0e")
                .queryParam("targetDt", "20120101");

        ResponseEntity<String> response = restTemplate.getForEntity(comp.toUriString(), String.class);

        System.out.println(response.getBody());

        JsonNode jsonNode = objectMapper.readTree(Objects.requireNonNull(response.getBody()));

        JsonNode at = jsonNode.at("/boxOfficeResult/dailyBoxOfficeList");

        List<String> movieNms = at.findValuesAsText("movieNm");

        for (String movieNm : movieNms) {
            System.out.println("movieNm = " + movieNm);
        }
    }
}

결과

 

[외부 API Get - getForEntity] dailyBoxOfficeList 의 movieNm 가져오기 - ObjectMapper.readValue + List<Map>
class ApiTest {

    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
        objectMapper = new ObjectMapper();
    }

    @Test
    void getForEntity() throws JsonProcessingException {

        UriComponentsBuilder comp = UriComponentsBuilder.newInstance()
                .scheme("http").host("www.kobis.or.kr")
                .path("kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
                .queryParam("key", "430156241533f1d058c603178cc3ca0e")
                .queryParam("targetDt", "20120101");

        ResponseEntity<String> response = restTemplate.getForEntity(comp.toUriString(), String.class);

        System.out.println(response.getBody());

        JsonNode jsonNode = objectMapper.readTree(Objects.requireNonNull(response.getBody()));

        JsonNode at = jsonNode.at("/boxOfficeResult/dailyBoxOfficeList");

        List<Map<String, Object>> mapList = objectMapper.readValue(at.toString(), new TypeReference<List<Map<String, Object>>>() {});

        for (Map<String, Object> dailyBoxOfficeList : mapList) {

            String movieNm = (String) dailyBoxOfficeList.get("movieNm");

            System.out.println("movieNm = " + movieNm);
        }
    }
}

결과

 

[외부 API Get - getForEntity] dailyBoxOfficeList 의 movieNm 가져오기 - ObjectMapper.readValue + CustomClass
class ApiTest {

    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
        objectMapper = new ObjectMapper();
    }

    @Test
    void getForEntity() throws JsonProcessingException {

        UriComponentsBuilder comp = UriComponentsBuilder.newInstance()
                .scheme("http").host("www.kobis.or.kr")
                .path("kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json")
                .queryParam("key", "430156241533f1d058c603178cc3ca0e")
                .queryParam("targetDt", "20120101");

        ResponseEntity<String> response = restTemplate.getForEntity(comp.toUriString(), String.class);

        System.out.println(response.getBody());

        JsonNode jsonNode = objectMapper.readTree(Objects.requireNonNull(response.getBody()));

        JsonNode at = jsonNode.at("/boxOfficeResult/dailyBoxOfficeList");

        List<DailyBoxOffice> mapList = objectMapper.readValue(at.toString(), new TypeReference<List<DailyBoxOffice>>(){});

        for (DailyBoxOffice dailyBoxOffice : mapList) {

            String movieNm = dailyBoxOffice.getMovieNm();

            System.out.println("movieNm = " + movieNm);
        }
    }
}
public class DailyBoxOffice {

    private String rnum;
    private String rank;
    private String rankInten;
    private String rankOldAndNew;
    private String movieCd;
    private String movieNm;
    private String openDt;
    private String salesAmt;
    private String salesShare;
    private String salesInten;
    private String salesChange;
    private String salesAcc;
    private String audiCnt;
    private String audiInten;
    private String audiChange;
    private String audiAcc;
    private String scrnCnt;
    private String showCnt;

    public String getRnum() {
        return rnum;
    }

    public void setRnum(String rnum) {
        this.rnum = rnum;
    }

    public String getRank() {
        return rank;
    }

    public void setRank(String rank) {
        this.rank = rank;
    }

    public String getRankInten() {
        return rankInten;
    }

    public void setRankInten(String rankInten) {
        this.rankInten = rankInten;
    }

    public String getRankOldAndNew() {
        return rankOldAndNew;
    }

    public void setRankOldAndNew(String rankOldAndNew) {
        this.rankOldAndNew = rankOldAndNew;
    }

    public String getMovieCd() {
        return movieCd;
    }

    public void setMovieCd(String movieCd) {
        this.movieCd = movieCd;
    }

    public String getMovieNm() {
        return movieNm;
    }

    public void setMovieNm(String movieNm) {
        this.movieNm = movieNm;
    }

    public String getOpenDt() {
        return openDt;
    }

    public void setOpenDt(String openDt) {
        this.openDt = openDt;
    }

    public String getSalesAmt() {
        return salesAmt;
    }

    public void setSalesAmt(String salesAmt) {
        this.salesAmt = salesAmt;
    }

    public String getSalesShare() {
        return salesShare;
    }

    public void setSalesShare(String salesShare) {
        this.salesShare = salesShare;
    }

    public String getSalesInten() {
        return salesInten;
    }

    public void setSalesInten(String salesInten) {
        this.salesInten = salesInten;
    }

    public String getSalesChange() {
        return salesChange;
    }

    public void setSalesChange(String salesChange) {
        this.salesChange = salesChange;
    }

    public String getSalesAcc() {
        return salesAcc;
    }

    public void setSalesAcc(String salesAcc) {
        this.salesAcc = salesAcc;
    }

    public String getAudiCnt() {
        return audiCnt;
    }

    public void setAudiCnt(String audiCnt) {
        this.audiCnt = audiCnt;
    }

    public String getAudiInten() {
        return audiInten;
    }

    public void setAudiInten(String audiInten) {
        this.audiInten = audiInten;
    }

    public String getAudiChange() {
        return audiChange;
    }

    public void setAudiChange(String audiChange) {
        this.audiChange = audiChange;
    }

    public String getAudiAcc() {
        return audiAcc;
    }

    public void setAudiAcc(String audiAcc) {
        this.audiAcc = audiAcc;
    }

    public String getScrnCnt() {
        return scrnCnt;
    }

    public void setScrnCnt(String scrnCnt) {
        this.scrnCnt = scrnCnt;
    }

    public String getShowCnt() {
        return showCnt;
    }

    public void setShowCnt(String showCnt) {
        this.showCnt = showCnt;
    }

    @Override
    public String toString() {
        return "DailyBoxOffice{" +
                "rnum='" + rnum + '\'' +
                ", rank='" + rank + '\'' +
                ", rankInten='" + rankInten + '\'' +
                ", rankOldAndNew='" + rankOldAndNew + '\'' +
                ", movieCd='" + movieCd + '\'' +
                ", movieNm='" + movieNm + '\'' +
                ", openDt='" + openDt + '\'' +
                ", salesAmt='" + salesAmt + '\'' +
                ", salesShare='" + salesShare + '\'' +
                ", salesInten='" + salesInten + '\'' +
                ", salesChange='" + salesChange + '\'' +
                ", salesAcc='" + salesAcc + '\'' +
                ", audiCnt='" + audiCnt + '\'' +
                ", audiInten='" + audiInten + '\'' +
                ", audiChange='" + audiChange + '\'' +
                ", audiAcc='" + audiAcc + '\'' +
                ", scrnCnt='" + scrnCnt + '\'' +
                ", showCnt='" + showCnt + '\'' +
                '}';
    }
}

결과

 

[외부 API Get - getForObject] getForObject - String.class
class ApiTest {

    private RestTemplate restTemplate;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
    }

    @Test
    void getForObject() {

        String url = "http://www.kobis.or.kr/kobisopenapi/webservice/rest/people/searchPeopleList.json?key={key}";

        String peopleListResult = restTemplate.getForObject(url, String.class, "430156241533f1d058c603178cc3ca0e");

        System.out.println(peopleListResult);


    }
}

결과

 

[외부 API Get - getForObject] getForObject - People 의 peopleNm 가져오기 + CustomClass
class ApiTest {

    private RestTemplate restTemplate;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
    }

    @Test
    void getForObject() {

        String url = "http://www.kobis.or.kr/kobisopenapi/webservice/rest/people/searchPeopleList.json?key={key}";

        ApiResult result = restTemplate.getForObject(url, ApiResult.class, "430156241533f1d058c603178cc3ca0e");

        System.out.println(result);

        for (People people : result.getPeopleListResult().getPeopleList()) {
            String peopleNm = people.getPeopleNm();
            System.out.println("peopleNm = " + peopleNm);
        }
    }
}
public class ApiResult {

    private PeopleListResult peopleListResult;

    public PeopleListResult getPeopleListResult() {
        return peopleListResult;
    }

    public void setPeopleListResult(PeopleListResult peopleListResult) {
        this.peopleListResult = peopleListResult;
    }

    @Override
    public String toString() {
        return "ApiResult{" +
                "peopleListResult=" + peopleListResult +
                '}';
    }
}
public class PeopleListResult {

    private Long totCnt;

    List<People> peopleList = new ArrayList<>();

    public Long getTotCnt() {
        return totCnt;
    }

    public void setTotCnt(Long totCnt) {
        this.totCnt = totCnt;
    }

    public List<People> getPeopleList() {
        return peopleList;
    }

    public void setPeopleList(List<People> peopleList) {
        this.peopleList = peopleList;
    }

    @Override
    public String toString() {
        return "PeopleListResult{" +
                "totCnt=" + totCnt +
                ", peopleList=" + peopleList +
                '}';
    }
}
public class People {

    private String peopleCd;
    private String peopleNm;
    private String peopleNmEn;
    private String repRoleNm;
    private String filmoNames;

    public String getPeopleCd() {
        return peopleCd;
    }

    public void setPeopleCd(String peopleCd) {
        this.peopleCd = peopleCd;
    }

    public String getPeopleNm() {
        return peopleNm;
    }

    public void setPeopleNm(String peopleNm) {
        this.peopleNm = peopleNm;
    }

    public String getPeopleNmEn() {
        return peopleNmEn;
    }

    public void setPeopleNmEn(String peopleNmEn) {
        this.peopleNmEn = peopleNmEn;
    }

    public String getRepRoleNm() {
        return repRoleNm;
    }

    public void setRepRoleNm(String repRoleNm) {
        this.repRoleNm = repRoleNm;
    }

    public String getFilmoNames() {
        return filmoNames;
    }

    public void setFilmoNames(String filmoNames) {
        this.filmoNames = filmoNames;
    }

    @Override
    public String toString() {
        return "People{" +
                "peopleCd='" + peopleCd + '\'' +
                ", peopleNm='" + peopleNm + '\'' +
                ", peopleNmEn='" + peopleNmEn + '\'' +
                ", repRoleNm='" + repRoleNm + '\'' +
                ", filmoNames='" + filmoNames + '\'' +
                '}';
    }
}

 

[내부 API POST - postForEntity] - 헤더 미포함
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void postForEntity() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book book = new Book();
        book.setAuthor("베르나르 베르베르");
        book.setPrice("8,880");
        book.setPublisher("열린책들");
        book.setTitle("나무");

        ResponseEntity<String> response = restTemplate.postForEntity(BASE_URL + "/books", book, String.class);

        System.out.println("response.getStatusCode() = " + response.getStatusCode());
        System.out.println("response.getBody() = " + response.getBody());
    }
}
public class Book {

    private String title;
    private String author;
    private String publisher;
    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> book (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            @RequestBody Book book) {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return new ResponseEntity<>(book, HttpStatus.CREATED);
    }
}

결과

 

[내부 API POST - postForEntity] - 헤더 포함
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void postForEntity() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book book = new Book();
        book.setAuthor("베르나르 베르베르");
        book.setPrice("8,880");
        book.setPublisher("열린책들");
        book.setTitle("나무");

        HttpHeaders headers = new HttpHeaders();
        headers.set("headerTest", "headerValue");
        HttpEntity<Book> request = new HttpEntity<>(book, headers);

        ResponseEntity<String> response = restTemplate.postForEntity(BASE_URL + "/books", request, String.class);

        System.out.println("response.getStatusCode() = " + response.getStatusCode());
        System.out.println("response.getBody() = " + response.getBody());
    }
}
public class Book {

    private String title;
    private String author;
    private String publisher;
    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> book (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            @RequestBody Book book) {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return new ResponseEntity<>(book, HttpStatus.CREATED);
    }
}

결과

 

[내부 API POST - postForLocation] 로케이션 가져오기
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void postForEntity() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book book = new Book();
        book.setAuthor("베르나르 베르베르");
        book.setPrice("8,880");
        book.setPublisher("열린책들");
        book.setTitle("나무");

        HttpHeaders headers = new HttpHeaders();
        headers.set("headerTest", "headerValue");
        HttpEntity<Book> request = new HttpEntity<>(book, headers);

        URI uri = restTemplate.postForLocation(BASE_URL + "/books", request);

        System.out.println("uri = " + uri);

    }
}
public class Book {

    private String title;
    private String author;
    private String publisher;
    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> book (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            @RequestBody Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }
}

결과

 

[내부 API POST - postForObject] CustomClass
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void postForObject() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book requestBook = new Book();
        requestBook.setAuthor("베르나르 베르베르");
        requestBook.setPrice("8,880");
        requestBook.setPublisher("열린책들");
        requestBook.setTitle("나무");

        Book responseBook = restTemplate.postForObject(BASE_URL + "/books", requestBook, Book.class);

        System.out.println("response = " + responseBook);
    }
}
public class Book {

    private String title;
    private String author;
    private String publisher;
    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> book (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            @RequestBody Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }
}

결과

 

[내부 API exchange] 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void exchange() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book requestBook = new Book();
        requestBook.setAuthor("베르나르 베르베르");
        requestBook.setPrice("8,880");
        requestBook.setPublisher("열린책들");
        requestBook.setTitle("나무");

        HttpEntity<Book> request = new HttpEntity<>(requestBook);

        ResponseEntity<Book> exchange = restTemplate.exchange(BASE_URL + "/books", HttpMethod.POST, request, Book.class);

        System.out.println("exchange.getStatusCode() = " + exchange.getStatusCode());

        Book responseBook = exchange.getBody();

        System.out.println("responseBook = " + responseBook);

    }
}
public class Book {

    private String title;
    private String author;
    private String publisher;
    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> book (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            @RequestBody Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }
}

결과

 

[내부 API POST - 폼 양식 데이터]
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void exchange() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book requestBook = new Book();
        requestBook.setAuthor("베르나르 베르베르");
        requestBook.setPrice("8,880");
        requestBook.setPublisher("열린책들");
        requestBook.setTitle("나무");


        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("title", "나무");
        map.add("author", "베르나르 베르베르");
        map.add("price", "8,800");
        map.add("publisher", "열린책들");

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);

        ResponseEntity<String> response = restTemplate.postForEntity(BASE_URL + "/books", request, String.class);

        System.out.println("response.getBody() = " + response.getBody());
    }
}
public class Book {

    private String title;
    private String author;
    private String publisher;
    private String price;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public String getPublisher() {
        return publisher;
    }

    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }

    public String getPrice() {
        return price;
    }

    public void setPrice(String price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", publisher='" + publisher + '\'' +
                ", price='" + price + '\'' +
                '}';
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> book (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }
}

결과

 

[내부 API 지원되는 매핑]
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void optionsForAllow() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Set<HttpMethod> httpMethods = restTemplate.optionsForAllow(BASE_URL + "/books");

        httpMethods.forEach(System.out::println);
        
        HttpMethod[] supportedMethod = {HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH};

        assertTrue(httpMethods.containsAll(Arrays.asList(supportedMethod)));
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> PostBook (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }

    @GetMapping("/books")
    public void GetBook() {
        log.info("GET");
    }

    @PutMapping("/books")
    public void PutBook() {
        log.info("PUT");
    }

    @PatchMapping("/books")
    public void PatchBook() {
        log.info("PATCH");
    }
}

결과

 

[내부 API - PUT] RequestCallback
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void exchange() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        Book requestBook = new Book();
        requestBook.setAuthor("베르나르 베르베르");
        requestBook.setPrice("8,880");
        requestBook.setPublisher("열린책들");
        requestBook.setTitle("나무");

        restTemplate.execute(BASE_URL + "/books", HttpMethod.PUT, requestCallback(requestBook), response -> null, "books");

    }

    private RequestCallback requestCallback(Book requestBook) {
        return request -> {
            ObjectMapper mapper = new ObjectMapper();

            mapper.writeValue(request.getBody(), requestBook);
            request.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
            request.getHeaders().add(HttpHeaders.AUTHORIZATION, "Basic " + "testpasswd");
        };
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> PostBook (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }

    @GetMapping("/books")
    public void GetBook() {
        log.info("GET");
    }

    @PutMapping("/books")
    public void PutBook(@RequestHeader("Content-Type") String contentType,
                        @RequestHeader("Authorization") String authorization) {
        log.info("PUT");
        log.info("content-type -> {}", contentType);
        log.info("authorization -> {}", authorization);
    }

    @PatchMapping("/books")
    public void PatchBook() {
        log.info("PATCH");
    }
}

결과

HttpEntity 와 다른방식이라고 생각하시면 됩니다

 

[HEAD - headForHeaders] HEAD 검색
class ApiTest {

    private RestTemplate restTemplate;

    @BeforeEach
    void setUp() {
        restTemplate = new RestTemplate();
    }

    @Test
    void header() {
        String url = "http://www.kobis.or.kr/kobisopenapi/webservice/rest/people/searchPeopleList.json?key={key}";

        HttpHeaders httpheaders = restTemplate.headForHeaders(url, "430156241533f1d058c603178cc3ca0e");

        System.out.println(httpheaders);

        assertEquals(httpheaders.get("X-Powered-By").get(0), "Servlet/2.5 JSP/2.1");
    }
}

결과

 

[내부 API - Delete]
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @LocalServerPort
    private int randomServerPort;

    @Test
    void exchange() {

        final String BASE_URL = "http://localhost:" + randomServerPort;

        restTemplate.delete(BASE_URL + "/books/{id}", 1L);
    }
}
@RestController
@Slf4j
public class Controller {

    @PostMapping("/books")
    public ResponseEntity<Book> PostBook (
            @RequestHeader(value = "headerTest", required = false) String headerTest,
            Book book) throws URISyntaxException {

        log.info("headerTest -> {}", headerTest);
        log.info("book -> {}", book);

        return ResponseEntity.created(new URI("http://localhost:8080/books")).body(book);
    }

    @GetMapping("/books")
    public void GetBook() {
        log.info("GET");
    }

    @PutMapping("/books")
    public void PutBook(@RequestHeader("Content-Type") String contentType,
                        @RequestHeader("Authorization") String authorization) {
        log.info("PUT");
        log.info("content-type -> {}", contentType);
        log.info("authorization -> {}", authorization);
    }

    @PatchMapping("/books")
    public void PatchBook() {
        log.info("PATCH");
    }

    @DeleteMapping("/books/{id}")
    public void DeleteBook(@PathVariable Long id) {
        log.info("delete -> {}", id);

    }
}

결과

 

[참고]

 

스프링 RestTemplate

Gatsby로 블로그 마이그레이션을 하여 이 링크를 클릭하면 해당 포스팅으로 갑니다. 감사합니다. http://blog.advenoh.pe.kr 1. 들어가며 스프링 프레임워크에서는 REST 서비스의 Endpoint를 호출할 수 있도�

advenoh.tistory.com

 

 

A Guide to the RestTemplate | Baeldung

Learn how to use the Spring RestTemplate to consume an API using all the main HTTP Verbs.

www.baeldung.com

 

 

Spring WebClient vs. RestTemplate | Baeldung

Learn how to make server-side HTTP calls using WebClient and RestTemplate.

www.baeldung.com

 

 

Spring 5 WebClient | Baeldung

Discover Spring 5's WebClient - a new reactive RestTemplate alternative.

www.baeldung.com

 

 

eugenp/tutorials

Just Announced - "Learn Spring Security OAuth": . Contribute to eugenp/tutorials development by creating an account on GitHub.

github.com

 

 

Jackson JsonNode

The Jackson JsonNode class is the Jackson tree object model for JSON. Jackson can read JSON into an object graph (tree) of JsonNode objects. Jackson can also write a JsonNode tree to JSON. This Jackson JsonNode tutorial explains how to work with the Jackso

tutorials.jenkov.com

 

 

[test] TestRestTemplate

Pet 프로젝트에 RestController를 통합 테스트 하기 위해 기존에는 주로 MockMvc 사용해서 구현을 많이 했었다. 테스트와 관련된 내용을 좀 찾아보고자 spring-boot test 문서를 보던중 Testing with a running se..

pipe0502.tistory.com

 

 

[Spring Boot #31] 스프링 부트 RestTemplate, WebClient

| RestTemplate, WebClient Spring 기반 프로젝트를 진행하면 컴포넌트 내부에서 URL을 요청해야하는 경우가 생깁니다. Spring에서는 Http 요청을 간단하게 이용할 수 있도록 Blocking I/O 기반의 RestTemplate,..

engkimbs.tistory.com

 

 

Heeeee

Vive en paz.

heeyeah.github.io

 

 

Spring boot TestRestTemplate POST with headers example

Spring boot TestRestTemplate POST using postForEntity() method example. Learn to test a post api accepting headers with Spring testresttemplate.

howtodoinjava.com

 

#5. WebClient TEST