01. JUnit 5 Test - MockMvc, Mockito, JPA [미완성]
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 |
# 파일
# 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
#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 를 설정해주면 된다
#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 코드 사용
[ 통합테스트 방법 ]
(참고 Gradle 빌드 툴에서는 Gradle execute 에서 Gradle:test 를 하면 된다고 함 - 안되면 위 그림처럼 하시면 됩니다)
[ 결과 ]
3. 데이터베이스 결합 테스트를 진행하는 경우 Repository를 지속적으로 DI 받아야 하는 문제는 아래 사이트 참고바람
#4. RestTemplate TEST
테스트용 API
메소드 | 설명 |
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);
}
}
[참고]
#5. WebClient TEST
'10. Spring > JUnit5' 카테고리의 다른 글
인프런 - 더 자바, 애플리케이션을 테스트하는 다양한 방법 (0) | 2021.07.30 |
---|