이전 개발 단계 보러 가기
https://taetae99.tistory.com/35
[Board] 벡엔드 토이 프로젝트 - 마이 페이지 구현 및 Dto와 Form 설계 (5)
이전 개발 단계 보러 가기https://taetae99.tistory.com/31 벡엔드 토이 프로젝트(게시판) - 로그인 및 게시판 메인화면 구현, Spring Security를 사용한 로그인 (이전 개발 단계 보러 가기 https://taetae99.tistor
taetae99.tistory.com
목차
1. 개요
프로젝트 진행 중 컨트롤러 테스트를 진행하려고 했다.
Spring Security를 사용하는 테스트의 경우 인증 권한이 필요한 경우에 대한 처리가 필요했다.
이때 사용하는 것이 Mock으로 Mock은 테스트를 위해 생성하는 실제 객체와 비슷한 가짜 객체를 의미한다.
Mock 객체로 테스트하는 방법엔 @WebMvcTest, @AutoConfigureMockMvc가 존재한다.
@WebMvcTest를 사용하면 @Controller, @RestController와 같은 웹레이어만 빈으로 등록하고
나머지 @Repository나 @Service들은 빈으로 등록하지 않는다.
대신, 필요한 경우 해당 빈들을 @Mockito를 사용해 Mock 객체로 주입하여 비교적 가볍게 테스트를 진행할 수 있다.
@AutoConfigureMockMvc를 사용하면 컨트롤러뿐만 아니라 @Service @Repositort 등도 빈으로 등록한다.
나는 테스트를 진행하며 @SpringBootTest와 @AuthConfigureMockMvc를 이용하여 테스트를 진행했다.
2. 테스트 코드 작성
1. 인증이 필요하지 않은 페이지에 대한 테스트
SpringSecurity 설정 시 로그인 페이지, 메인 화면, 회원가입 페이지는 인증되지 않은 사용자 또한 열람할 수 있었다.
이에 대해 인증이 필요하지 않은 페이지에 대한 요청을 확인해보겠다.
이때 @WithAnonymousUser 어노테이션을 사용한다.
@Test
@WithAnonymousUser
void 회원가입_페이지() throws Exception {
mockMvc.perform(get("/auth/register"))
.andExpect(status().isOk())
.andExpect(view().name("member/memberJoin"));
}
@Test
@WithAnonymousUser
void 회원가입_페이지_실패() throws Exception {
mockMvc.perform(get("/auth/register1"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/auth/login"));
}
@Test
@WithAnonymousUser
void 로그인_페이지() throws Exception {
mockMvc.perform(get("/auth/login"))
.andExpect(status().isOk())
.andExpect(view().name("member/loginForm"));
}
@Test
@WithAnonymousUser
void 로그인_페이지_실패() throws Exception {
mockMvc.perform(get("/auth/login1"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/auth/login"));
}
회원가입_페이지_실패 와 로그인_페이지_실패의 경우에는 페이지 가져오기에 실패하는 경우
SpringSecurity가 자동으로 로그인 페이지로 리다이렉트 해주기 때문에 위와 같이 작성하였다.
2. 인증이 필요한 페이지에 대한 테스트
@Test
@WithMockUser
void 글쓰기_페이지() throws Exception {
mockMvc.perform(get("/board/write"))
.andExpect(status().isOk())
.andExpect(view().name("post/createPostForm"));
}
인증이 필요한 페이지인 "/board/write"에서는 @WithMockUser를 통해 인증된 사용자를 모의로 생성한다.
때문에 @WithMockUser 어노테이션이 없다면 테스트는 실패한다.
3. 로그인
@Test
@WithAnonymousUser
void 로그인() throws Exception {
//회원 가입
String password = "12345678";
Member member = Member.createMember("테스터","test@spring.com",
bCryptPasswordEncoder.encode(password),
"test");
Long id = memberService.join(member);
Member findMember = memberService.findOne(id);
//Test
mockMvc.perform(post("/auth/login")
.param("email", findMember.getEmail())
.param("password", password))
.andExpect(authenticated())
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/board"));
}
회원가입을 진행하고 회원가입한 데이터로 로그인에 성공하는지 테스트한다.
4. 로그인 실패
@Test
@WithAnonymousUser
void 로그인_실패() throws Exception {
//회원 가입
String password = "12345678";
Member member = Member.createMember("테스터","test@spring.com",
bCryptPasswordEncoder.encode(password),
"test");
Long id = memberService.join(member);
Member findMember = memberService.findOne(id);
mockMvc.perform(post("/auth/login")
.param("email", findMember.getEmail())
.param("password", "wrong_password"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/auth/login?error=true"));
}
잘못된 정보로 로그인을 시도했을 때 Spring Security Config에 지정해 둔 실패 시 리다이렉트 되는 Url("/auth/login?error=true")로 리다이렉트 되는지 확인한다.
5. 로그아웃
@Test
@WithMockUser
void 로그아웃() throws Exception {
mockMvc.perform(get("/auth/logout"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/auth/login"));
}
6. 회원 가입
@Test
@WithAnonymousUser
void 회원가입() throws Exception {
mockMvc.perform(post("/auth/register")
.param("name", "코코")
.param("email", "test@spring.com")
.param("password", "12345678")
.param("nickname", "nick"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/auth/login"));
}
7. 회원 가입 실패
@Test
@WithAnonymousUser
void 회원가입_실패() throws Exception{
mockMvc.perform(post("/auth/register")
.param("name", "")
.param("email", "12345678")
.param("password", "test@example.com")
.param("nickname", "nick"))
.andExpect(status().isOk())
.andExpect(view().name("member/memberJoin"))
.andExpect(model().attributeHasFieldErrors("memberForm", "name"));
}
회원 가입의 경우 @Valid를 통한 검증을 한다.
param을 통해 이름을 빈칸으로 전달하였을 때 attributeHasFieldErrors를 통해 name 필드의 유효성 검사 실패 여부를 확인한다.
8. 마이페이지
@Test
void 마이페이지() throws Exception {
String password = "12345678";
Member member = Member.createMember("테스터","test@spring.com",
bCryptPasswordEncoder.encode(password),
"test");
memberService.join(member);
mockMvc.perform(get("/auth/mypage")
.with(SecurityMockMvcRequestPostProcessors.user(new MemberDetail(member))))
.andExpect(status().isOk())
.andExpect(view().name("member/myPageForm"));
}
마이페이지에는 @AuthenticationPrincipal으로 현재 로그인된 사용자의 정보를 받아야 한다.
이를 SecurityMockMvcRequestPostProcessors로 전달한다.
3. 전체 코드
@SpringBootTest
@AutoConfigureMockMvc
class MemberControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
MemberService memberService;
@Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
@Test
@WithAnonymousUser
void 로그인() throws Exception {
//회원 가입
String password = "12345678";
Member member = Member.createMember("테스터","test@spring.com",
bCryptPasswordEncoder.encode(password),
"test");
Long id = memberService.join(member);
Member findMember = memberService.findOne(id);
//Test
mockMvc.perform(post("/auth/login")
.param("email", findMember.getEmail())
.param("password", password))
.andExpect(authenticated())
.andExpect(status().is3xxRedirection()) // 성공 시 리다이렉트 확인
.andExpect(redirectedUrl("/board"));
}
@Test
@WithAnonymousUser
void 로그인_실패() throws Exception {
//회원 가입
String password = "12345678";
Member member = Member.createMember("테스터","test@spring.com",
bCryptPasswordEncoder.encode(password),
"test");
Long id = memberService.join(member);
Member findMember = memberService.findOne(id);
mockMvc.perform(post("/auth/login")
.param("email", findMember.getEmail())
.param("password", "wrong_password"))
.andExpect(status().is3xxRedirection()) // 리다이렉트 상태 코드 확인
.andExpect(redirectedUrl("/auth/login?error=true"));
}
@Test
@WithAnonymousUser
void 회원가입() throws Exception {
mockMvc.perform(post("/auth/register")
.param("name", "코코")
.param("email", "test@spring.com")
.param("password", "12345678")
.param("nickname", "nick"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/auth/login"));
}
@Test
@WithAnonymousUser
void 회원가입_실패() throws Exception{
mockMvc.perform(post("/auth/register")
.param("name", "")
.param("email", "12345678")
.param("password", "test@example.com")
.param("nickname", "nick"))
.andExpect(status().isOk())
.andExpect(view().name("member/memberJoin"))
.andExpect(model().attributeHasFieldErrors("memberForm", "name"));
}
@Test
void 마이페이지() throws Exception {
String password = "12345678";
Member member = Member.createMember("테스터","test@spring.com",
bCryptPasswordEncoder.encode(password),
"test");
memberService.join(member);
mockMvc.perform(get("/auth/mypage")
.with(SecurityMockMvcRequestPostProcessors.user(new MemberDetail(member))))
.andExpect(status().isOk())
.andExpect(view().name("member/myPageForm"));
}
/**
* @WithMockUser가 없으면 인증되지 않은 상태 이므로
* 실패함
*/
@Test
@WithMockUser
void 글쓰기_페이지() throws Exception {
mockMvc.perform(get("/board/write"))
.andExpect(status().isOk())
.andExpect(view().name("post/createPostForm"));
}
@Test
@WithMockUser
void 로그아웃() throws Exception {
mockMvc.perform(get("/auth/logout"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/auth/login"));
}
@Test
@WithAnonymousUser
void 회원가입_페이지() throws Exception {
mockMvc.perform(get("/auth/register"))
.andExpect(status().isOk())
.andExpect(view().name("member/memberJoin"));
}
@Test
@WithAnonymousUser
void 회원가입_페이지_실패() throws Exception {
mockMvc.perform(get("/auth/register1"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/auth/login"));
}
@Test
@WithAnonymousUser
void 로그인_페이지() throws Exception {
mockMvc.perform(get("/auth/login"))
.andExpect(status().isOk())
.andExpect(view().name("member/loginForm"));
}
@Test
@WithAnonymousUser
void 로그인_페이지_실패() throws Exception {
mockMvc.perform(get("/auth/login1"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("http://localhost/auth/login"));
}
}
'프로젝트 > Board 프로젝트' 카테고리의 다른 글
[JPA] 게시판 프로젝트 - 게시글 수정, 삭제 기능 개발 (8) (1) | 2025.02.09 |
---|---|
[JPA] 게시판 프로젝트 - 게시글 생성 기능 구현 (7) (1) | 2025.02.03 |
[JPA] 게시판 프로젝트 - 마이 페이지 구현 및 Dto와 Form 설계 (5) (0) | 2025.01.24 |
[JPA] 게시판 프로젝트 - 로그인 및 게시판 메인화면 구현, Spring Security를 사용한 로그인 (4) (0) | 2025.01.17 |