- Published on
살면서 처음 해보는 통합테스트
- Authors
- Name
- 불타는 라쿤들
🦡 작성자 : 아연
살면서 처음 해보는 통합테스트
그래서 그냥 흥청망청 테스트 됨
처음 내 시나리오는
- User 만들고
- Mentoring 만들어서
- User의 상태별 멘토링 조회하면
- Mentoring이 나온다.
였는데 이대로 한 건지는 잘 모르겠다 .. 하핫
일단 모든 통합테스트에서 사용될 Base Class 를 만들자.
@SpringBootTest
@AutoConfigureMockMvc
@Disabled
@Transactional
public class IntegrationTest {
@Autowired
protected MockMvc mvc;
@Autowired
private WebApplicationContext ctx;
@BeforeEach
void setUp() {
mvc = MockMvcBuilders
.webAppContextSetup(ctx)
.apply(springSecurity())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
.build();
}
}
Mock MVC를 설정하려면 MockMvcBuilders를 사용한다. 이 클래스는 정적 메서드 두 개를 제공한다.
- standaloneSetup() : 수동으로 생성하고 구성한 컨트롤러 한 개 이상을 서비스할 Mock MVC를 만든다.
- webAppContextSetup() : 구성된 컨트롤러 한 개 이상을 포함하는 스프링 애플리케이션 컨텍스트를 사용하여 Mock MVC를 만든다.
이제 내가 원하는 사용자로 실행하는 테스트가 필요하다. 이를 위해서는 요청에 인증 정보를 추가해야 하는데 RequestPostProcessor
또는 Annotation
을 사용할 수 있다.
1. RequestPostProcessor 사용하기
- UserRequestPostProcessor.user()
mvc
.perform(get("/").with(user("user")))
mvc
.perform(get("/admin").with(user("admin").password("pass").roles("USER","ADMIN")))
여기서 user
는
public static UserRequestPostProcessor user(String username) {
return new UserRequestPostProcessor(username);
}
얘다.
- 그 외
mvc
.perform(get("/").with(anonymous()))
mvc
.perform(get("/").with(authentication(authentication)))
mvc
.perform(get("/").with(user(userDetails)))
mvc
.perform(get("/").with(securityContext(securityContext)))
2. Annotaions 사용하기
Spring Security 테스트 지원을 사용하기 전에 설정이 필요하다.
@ExtendWith(SpringExtension.class)
@ContextConfiguration
public class WithMockUserTests {
// ...
}
- @WithMockUser
@WithMockUser
@WithMockUser(username="admin",roles={"USER","ADMIN"})
// 값에 ROLE_ 접두사가 붙지 않게 하고싶으면
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
- 그 외
@WithAnonymousUser
// userDetailsServiceBeanName은 UserDetailsService 빈 이름이다.
@WithUserDetails(value="username", userDetailsServiceBeanName="authDetailsService")
기본적으로 보안 컨텍스트는 JUnit의 @Before
이전에 발생한다. @WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)
으로 설정하면 JUnit의 @Before
이후에 발생한다.
(( JPA 쿼리 Method 를 사용했을 때 잘 돌아가던 코드 ^^,, 지금은 안 됨 ))
class MentoringControllerTest extends IntegrationTest {
private AuthDetails authDetails;
@BeforeEach
void setUp() {
User user1 = new User(1L, 1L, "mail", "후배", "011", "profile", 0, Role.USER, true, now(), now(), false);
authDetails = new AuthDetails(user1);
Authentication auth = new UsernamePasswordAuthenticationToken(authDetails, "", authDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
// Create and save the user
User user2 = new User(100L, 2L, "mail", "선배", "01011111111", "profile", 0, Role.SENIOR, true, LocalDateTime.now(), LocalDateTime.now(), false);
Senior senior = new Senior(1L, user2, "a", Status.WAITING, 100, new Info(), new Profile(), now(), now());
// Create and save the mentoring
Mentoring mentoring = new Mentoring(99L, user1, senior, "topic", "question", "date", 40, 20000, Status.WAITING, LocalDateTime.now(), LocalDateTime.now());
}
@Test
@DisplayName("대학생이 신청한 멘토링 목록 조회")
void getWaitingMentorings() throws Exception {
mvc.perform(get("/mentoring/me/waiting").with(user(authDetails)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("MT200"))
.andExpect(jsonPath("$.message").value("멘토링 리스트 조회에 성공하였습니다."))
.andExpect(jsonPath("$.data").isNotEmpty());
}
}
위에서 with(user("user"))
도 써보고 @WithMockUser
도 다 써 봤는데 통과는 함. 문제는 mentoring
을 담은 mentoringInfos
이 반환될 줄 알았는데 계속 빈 리스트만 반환됨 ㅜㅅㅜ
그래서 그냥 response의
- status - 200
- code - "MT200"
- message - "멘토링 리스트 조회에 성공하였습니다."
- data - isNotEmpty()
만으로 만족하려고 했다. 일단 응답이 오는 데 오류는 안나니까 . . .
이것들 다 써보고 알았다. 나는 지금 security를 통과할 인증 정보가 필요한 게 아니라 멘토링을 가진 유저의 토큰이 필요하다는 것 😇 . .
그래서
- 토큰을 발급할 유저(
user
) 만들고 - 선배(
senior
,userOfSenior
) 도 만들고 - 멘토링(
mentoring
)도 만들었다
class MentoringControllerTest extends IntegrationTest {
@Autowired
private JwtUtils jwtUtil;
@Autowired
private UserRepository userRepository;
@Autowired
private SeniorRepository seniorRepository;
@Autowired
private MentoringRepository mentoringRepository;
private User user;
private Senior senior;
private Long userId;
private String accessToken;
@BeforeEach
void setUp() {
user = new User(0L, 1L, "mail", "후배", "011", "profile", 0, Role.USER, true, now(), now(), false);
userRepository.save(user);
userId = user.getUserId();
User userOfSenior = new User(0L, 2L, "mail", "선배", "012", "profile", 0, Role.SENIOR, true, now(), now(), false);
userRepository.save(userOfSenior);
Info info = new Info("major", "서울대학교", "교수님", "키워드1,키워드2", "랩실", "인공지능", false, false, "인공지능,키워드1,키워드2");
Profile profile = new Profile("저는요", "한줄소개", "대상", "chatLink", 40);
senior = new Senior(0L, userOfSenior, "certification", WAITING, 0, info, profile, now(), now());
seniorRepository.save(senior);
accessToken = jwtUtil.generateAccessToken(userId, Role.USER);
}
@Test
@DisplayName("대학생이 신청한 멘토링 목록을 조회한다")
void getWaitingMentorings() throws Exception {
Mentoring waitingMentoring = new Mentoring(0L, user, senior, "topic", "question", "date", 40, Status.WAITING, now(), now());
mentoringRepository.save(waitingMentoring);
mvc.perform(get("/mentoring/me/waiting")
.header("Authorization", "Bearer " + accessToken))
.andDo(print());
}
{
"code": "MT200",
"message": "멘토링 리스트 조회에 성공하였습니다.",
"data": {
"mentoringInfos": [
{
"mentoringId": 185,
"seniorId": 114,
"profile": "profile",
"nickName": "선배",
"postgradu": "서울대학교",
"major": "major",
"lab": "랩실",
"term": 40
}
]
}
}
이제야 원하는 멘토링이 나왔다.
그럼 정말 값이 올바른지 테스트해보자.
@Test
@DisplayName("대학생이 신청한 멘토링 목록을 조회한다")
void getWaitingMentorings() throws Exception {
Mentoring waitingMentoring = new Mentoring(0L, user, senior, "topic", "question", "date", 40, Status.WAITING, now(), now());
mentoringRepository.save(waitingMentoring);
mvc.perform(get("/mentoring/me/waiting")
.header("Authorization", "Bearer " + accessToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value("MT200"))
.andExpect(jsonPath("$.message").value("멘토링 리스트 조회에 성공하였습니다."))
.andExpect(jsonPath("$.data.mentoringInfos[0].profile").value(containsString("profile")))
.andExpect(jsonPath("$.data.mentoringInfos[0].nickName").value(containsString("선배")))
.andExpect(jsonPath("$.data.mentoringInfos[0].postgradu").value(containsString("서울대학교")))
.andExpect(jsonPath("$.data.mentoringInfos[0].major").value(containsString("major")))
.andExpect(jsonPath("$.data.mentoringInfos[0].lab").value(containsString("랩실")))
.andExpect(jsonPath("$.data.mentoringInfos[0].term").value(40))
.andExpect(jsonPath("$.data.mentoringInfos[0].chatLink").doesNotExist())
.andDo(print());
}
JsonPath는 json 객체를 탐색하기 위한 표준화된 방법이다.
- 점 표기법
$.tool.jsonpath.creator.location[2]
- 대괄호 표기법
$['tool']['jsonpath']['creator']['location'][2]
자세한 표기법은 아래를 참고하자.
Introduction to JsonPath | Baeldung
통합테스트의 단점은
내가 잘못써서 오래걸리는 건지
원래 오래걸리는 건지
쩝 . . .