본문 바로가기
프로젝트/Board 프로젝트

[JPA] 게시판 프로젝트 - MemberController MockMvc를 사용하여 테스트하기 (6)

by taetae99 2025. 1. 27.
반응형

 

이전 개발 단계 보러 가기

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")로 리다이렉트 되는지 확인한다.

    config
    config 설정 내용

     

     

    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"));
        }
    
    
    }
    반응형