티스토리 뷰
스프링 부트 웹 애플리케이션 제작(15): 로그인, 로그아웃 테스트 작성
Jaime.Lee 2022. 1. 17. 10:30
본 포스팅은 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발 강의를 참고하여 작성하였습니다.
소스 코드는 여기 있습니다. (commit hash: 51d546d)> git clone https://github.com/lcalmsky/spring-boot-app.git > git checkout 51d546d
ℹ️ squash merge를 사용해 기존 branch를 삭제하기로 하여 앞으로는 commit hash로 포스팅 시점의 소스 코드를 공유할 예정입니다.
Overview
이전 포스팅에서 구현한 로그인과 로그아웃 기능을 테스트합니다.
Tips
테스트를 작성하기에 앞서 이전 포스팅에서 다뤘던 내용 중 /login
을 호출할 때 반드시 username
과 password
파라미터를 전달해야 한다는 부분이 있었는데요, 이는 spring security 기본 설정이고 파라미터명을 변경하기 위해선 SecurityConfig
클래스를 수정해줘야 합니다.
/src/main/java/io/lcalmsky/app/config/SecurityConfig.java
// 생략
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/", "/login", "/sign-up", "/check-email-token",
"/email-login", "/check-email-login", "/login-link").permitAll()
.mvcMatchers(HttpMethod.GET, "/profile/*").permitAll()
.anyRequest().authenticated();
http.formLogin()
.loginPage("/login")
.usernameParameter("id") // (1)
.passwordParameter("pw") // (2)
.permitAll();
http.logout()
.logoutSuccessUrl("/");
}
// 생략
}
username
을id
로 바꿀 수 있습니다.password
를pw
로 바꿀 수 있습니다.
Test
먼저 이메일로 로그인 테스트를 작성해보겠습니다.
package io.lcalmsky.app.main.endpoint.controller;
import io.lcalmsky.app.account.application.AccountService;
import io.lcalmsky.app.account.endpoint.controller.SignUpForm;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MainControllerTest {
@Autowired MockMvc mockMvc;
@Autowired AccountService accountService;
@Test
@DisplayName("이메일로 로그인: 성공")
void login_with_email() throws Exception {
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname("jaime");
signUpForm.setEmail("lcalmsky@gmail.com");
signUpForm.setPassword("test1234");
accountService.signUp(signUpForm); // (1)
mockMvc.perform(post("/login") // (2)
.param("username", "lcalmsky@gmail.com") // (2)
.param("password", "test1234") // (2)
.with(csrf())) // (3)
.andExpect(status().is3xxRedirection()) // (4)
.andExpect(redirectedUrl("/")) // (5)
.andExpect(authenticated().withUsername("jaime")); // (6)
}
}
AccountService
를 이용해 테스트할 계정을 가입시킵니다./login
을 호출합니다.parameter
로username
과password
를 전달합니다.spring security
를 사용했기 때문에csrf
요청이 필요합니다.- 결과는 로그인 된 이후 redirect 응답을 받아야 합니다.
- redirect된 url은 루트("/")가 되어야 합니다.
- 인증이 되어야 하고 이 때
username
은nickname
이 되어야 합니다. 그 이유는UserAccount
클래스에서 부모 클래스의 생성자를 호출할 때 이메일이 아닌nickname
을 전달했기 때문입니다.
성공한 것을 확인할 수 있습니다.
다음으로 nickname으로 로그인 테스트를 작성해보겠습니다.
이전 코드와 매우 유사하기 때문에 나머지 부분은 생략하겠습니다.
@Test
@DisplayName("닉네임으로 로그인: 성공")
void login_with_nickname() throws Exception {
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname("jaime");
signUpForm.setEmail("lcalmsky@gmail.com");
signUpForm.setPassword("test1234");
accountService.signUp(signUpForm);
mockMvc.perform(post("/login")
.param("username", "jaime")
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"))
.andExpect(authenticated().withUsername("jaime"));
}
마찬가지로 성공한 것을 확인할 수 있습니다.
방금 두 코드는 메서드로 나눠져있지만 매우 많은 부분의 중복된 코드가 존재합니다.
그래서 계정을 가입시키는 부분을 따로 메서드로 추출하겠습니다.
@BeforeEach
를 이용해 매 테스트 이전 반복적으로 수행되게 할 수 있습니다.
대신 이렇게 진행했을 경우 다음 테스트에 영향을 줄 수 있기 때문에 @AfterEach
를 이용해 DB의 데이터를 모두 지워줍니다.
최종적으로 아래 처럼 수정하였습니다.
package io.lcalmsky.app.main.endpoint.controller;
import io.lcalmsky.app.account.application.AccountService;
import io.lcalmsky.app.account.endpoint.controller.SignUpForm;
import io.lcalmsky.app.account.infra.repository.AccountRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MainControllerTest {
@Autowired MockMvc mockMvc;
@Autowired AccountService accountService;
@Autowired AccountRepository accountRepository;
@BeforeEach
void beforeEach() {
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname("jaime");
signUpForm.setEmail("lcalmsky@gmail.com");
signUpForm.setPassword("test1234");
accountService.signUp(signUpForm);
}
@AfterEach
void afterEach() {
accountRepository.deleteAll();
}
@Test
@DisplayName("이메일로 로그인: 성공")
void login_with_email() throws Exception {
mockMvc.perform(post("/login")
.param("username", "lcalmsky@gmail.com")
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"))
.andExpect(authenticated().withUsername("jaime"));
}
@Test
@DisplayName("닉네임으로 로그인: 성공")
void login_with_nickname() throws Exception {
mockMvc.perform(post("/login")
.param("username", "jaime")
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"))
.andExpect(authenticated().withUsername("jaime"));
}
}
다음으로 로그인 실패 테스트를 작성해보겠습니다.
@Test
@DisplayName("로그인 실패")
void login_fail() throws Exception {
mockMvc.perform(post("/login")
.param("username", "test") // (1)
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection()) // (2)
.andExpect(redirectedUrl("/login?error")) // (3)
.andExpect(unauthenticated()); // (4)
}
- 가입할 때 사용하지 않은
nickname
을 적어줍니다. - 실패시에도 redirect 됩니다.
- redirect 되는 url은 /login?error 인데요, 이는 spring security에서 자동으로 처리해주는 부분입니다.
- 로그인에 실패하였기 때문에 인증되지 않은 상태로 남아있습니다.
마찬가지로 성공한 것을 확인할 수 있습니다.
마지막으로 로그아웃 테스트를 해보겠습니다.
@Test
@DisplayName("로그아웃: 성공")
void logout() throws Exception {
mockMvc.perform(post("/logout") // (1)
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/")) // (2)
.andExpect(unauthenticated()); // (3)
}
- /logout 요청을 합니다.
- logout 이후에는 루트("/")로 redirect 되어야 합니다.
- logout 하였기 때문에 인증되지 않은 상태가 되어야 합니다.
성공한 것을 확인할 수 있습니다.
MainControllerTest.java 전체 보기
package io.lcalmsky.app.main.endpoint.controller;
import io.lcalmsky.app.account.application.AccountService;
import io.lcalmsky.app.account.endpoint.controller.SignUpForm;
import io.lcalmsky.app.account.infra.repository.AccountRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
class MainControllerTest {
@Autowired MockMvc mockMvc;
@Autowired AccountService accountService;
@Autowired AccountRepository accountRepository;
@BeforeEach
void beforeEach() {
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname("jaime");
signUpForm.setEmail("lcalmsky@gmail.com");
signUpForm.setPassword("test1234");
accountService.signUp(signUpForm);
}
@AfterEach
void afterEach() {
accountRepository.deleteAll();
}
@Test
@DisplayName("이메일로 로그인: 성공")
void login_with_email() throws Exception {
mockMvc.perform(post("/login")
.param("username", "lcalmsky@gmail.com")
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"))
.andExpect(authenticated().withUsername("jaime"));
}
@Test
@DisplayName("닉네임으로 로그인: 성공")
void login_with_nickname() throws Exception {
mockMvc.perform(post("/login")
.param("username", "jaime")
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"))
.andExpect(authenticated().withUsername("jaime"));
}
@Test
@DisplayName("로그인: 실패")
void login_fail() throws Exception {
mockMvc.perform(post("/login")
.param("username", "test")
.param("password", "test1234")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/login?error"))
.andExpect(unauthenticated());
}
@Test
@DisplayName("로그아웃: 성공")
void logout() throws Exception {
mockMvc.perform(post("/logout")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/"))
.andExpect(unauthenticated());
}
}
'SpringBoot > Web Application 만들기' 카테고리의 다른 글
스프링 부트 웹 애플리케이션 제작(17): 프로필 뷰 구현 (0) | 2022.01.26 |
---|---|
스프링 부트 웹 애플리케이션 제작(16): 로그인 유지(RememberMe) 기능 구현 (0) | 2022.01.20 |
스프링 부트 웹 애플리케이션 제작(14): 로그인, 로그아웃 기능 구현 (0) | 2022.01.16 |
스프링 부트 웹 애플리케이션 제작(13): 인증 이메일 확인 유도 및 재전송 (2) | 2022.01.13 |
스프링 부트 웹 애플리케이션 제작(12): 인증된 사용자 정보 참조 (0) | 2021.12.30 |
- Total
- Today
- Yesterday
- QueryDSL
- Jackson
- gRPC
- 스프링 부트 튜토리얼
- proto3
- @ManyToOne
- 헥사고날 아키텍처
- 스프링부트
- Java
- spring boot app
- leetcode
- r
- JSON
- Spring Boot JPA
- 함께 자라기
- Spring Data JPA
- spring boot application
- Spring Boot Tutorial
- 알고리즘
- 클린 아키텍처
- Spring Boot
- Linux
- 스프링 부트 애플리케이션
- intellij
- 스프링 데이터 jpa
- 스프링 부트 회원 가입
- 함께 자라기 후기
- spring boot jwt
- 스프링 부트
- 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 |