본문 바로가기

Dot Programming/Spring Clone

[스프링 웹앱 프로젝트 #25] 프로필 수정 테스트

25. 프로필 수정 테스트

인증된 사용자
가 접근할 수 있는 기능 테스트하기
 > 실제 DB에 저장되어 있는 정보에 대응하는 인증된 Authentication이 필요하다.
 > @WithMockUser로는 처리할 수 없다

인증된 사용자를 제공할 커스텀 어노테이션 만들기
 > @WithAccount


커스텀 어노테이션 
@WithSecurityContext

SecurityContextFactory 구현

참고문서
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#test

 

 

Junit5 - SpringSecurity Test 버그

@SpringBootTest
@AutoConfigureMockMvc
class SettingsControllerTest {
    
    @Autowired
    MockMvc mockMvc;
    
    @Autowired
    AccountService accountService;
    
    @Autowired
    AccountRepository accountRepository;

    @BeforeEach
    void beforeEach(){
        SignUpForm signUpForm = new SignUpForm();
        signUpForm.setNickname("loosie");
        signUpForm.setEmail("loosie@naver.com");
        signUpForm.setPassword("12341234");
        accountService.processNewAccount(signUpForm);
    }

    @WithUserDetails(value = "loosie", setupBefore = TestExecutionEvent.TEST_EXECUTION)
    @DisplayName("프로필 수정하기 - 입력값 정상")
    @Test
    void updateProfile() throws Exception{
        String bio = "짧은 소개를 수정하는 경우";
        mockMvc.perform(post(SettingsController.SETTINGS_PROFILE_URL)
                    .param("bio", bio)
                    .with(csrf()))
                    .andExpect(status().is3xxRedirection())
                    .andExpect(redirectedUrl(SettingsController.SETTINGS_PROFILE_URL))
                    .andExpect(flash().attributeExists("message"));

        Account loosie = accountRepository.findByNickname("loosie");
        assertEquals(bio, loosie.getBio());
     }
       

}

 

BUG

beforeEach()실행 되기 전에 WithUserDetail이 실행이 되어서 유저 값을 못받아온다

'setupBefore = TestExecutionEvent.TEST_EXECUTION' beforeEach와 Test코드 중간에 실행하게 해주는 기능인데

이를 선언해주어도 test는 실패한다.

 > 오픈소스 풀리퀘날려도 됨

 

 

프로필 수정하는 테스트 작성

그래서 @WithUserDetails말고 @WithSecurityContext를 사용할 것이다.

 

@Interface WithUser 생성

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithAccountSecurityContextFactory.class)
public @interface WithAccount {
    String value();
}

 

유저 데이터폼을 저장 후 인증된 회원정보를 context에 담는 클래스 생성

WithAccountSecurityContextFactory.class 

@RequiredArgsConstructor
public class WithAccountSecurityContextFactory implements WithSecurityContextFactory<WithAccount> {

    private final AccountService accountService;

    @Override
    public SecurityContext createSecurityContext(WithAccount withAccount) {
        String nickname = withAccount.value();

        SignUpForm signUpForm = new SignUpForm();
        signUpForm.setNickname(nickname);
        signUpForm.setEmail(nickname + "@naver.com");
        signUpForm.setPassword("12341234");
        accountService.processNewAccount(signUpForm);

        UserDetails principal = accountService.loadUserByUsername(nickname);
        Authentication authentication =  new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);

        return context;
    }
}

 

Test 실행

(@AfterEach : 테스트 실행 후 @WithAccount로 생성된 인증회원정보를 삭제해 줌)

@SpringBootTest
@AutoConfigureMockMvc
class SettingsControllerTest {
    
    @Autowired
    MockMvc mockMvc;

    @Autowired
    AccountRepository accountRepository;

    // 테스트 실행 후에는 테스트로 만든 계정을 다시 지워야함
    @AfterEach
    void afterEach(){
        accountRepository.deleteAll();
    }


    @WithAccount("loosie")
    @DisplayName("프로필 수정하기 - 입력값 정상")
    @Test
    void updateProfile() throws Exception{
        String bio = "짧은 소개를 수정하는 경우";
        mockMvc.perform(post(SettingsController.SETTINGS_PROFILE_URL)
                    .param("bio", bio)
                    .with(csrf()))
                    .andExpect(status().is3xxRedirection())
                    .andExpect(redirectedUrl(SettingsController.SETTINGS_PROFILE_URL))
                    .andExpect(flash().attributeExists("message"));

        Account loosie = accountRepository.findByNickname("loosie");
        assertEquals(bio, loosie.getBio());
        
     }
       

}

 

 

프로필 수정값 에러 테스트 작성 

bio 35자 제한

    @WithAccount("loosie")
    @DisplayName("프로필 수정하기 - 입력값 에러")
    @Test
    void updateProfile_error() throws Exception{
        String bio = "35자 이상 에러 길게 소개를 수정하는 경우길게 소개를 수정하는 경우길게 소개를 수정하는 경우길게 소개를 수정하는@Length(max = 50)@Length(max = 50)@Length(max = 50)@Length(max = 50)@Length(max = 50)@Length(max = 50) 경우길게 소개를 수정하는 경우";
        mockMvc.perform(post(SettingsController.SETTINGS_PROFILE_URL)
                .param("bio", bio)
                .with(csrf()))
                .andExpect(status().isOk())
                .andExpect(view().name(SettingsController.SETTINGS_PROFILE_VIEW_NAME))
                .andExpect(model().attributeExists("account"))
                .andExpect(model().attributeExists("profile"))
                .andExpect(model().hasErrors());

        Account loosie = accountRepository.findByNickname("loosie");
        assertNull(loosie.getBio());

    }

 

마지막으로 프로필 수정폼 가져오는 테스트 작성

인증된 유저 객체가 없으면 에러

@WithAccount("loosie")  // 인증된 유저정보 없으면 오류
    @DisplayName("프로필 수정 폼")
    @Test
    void updateProfileForm() throws Exception{
        mockMvc.perform(get(SettingsController.SETTINGS_PROFILE_URL))
                .andExpect(status().isOk())
                .andExpect(model().attributeExists("account"))
                .andExpect(model().attributeExists("profile"));
    }

 


참고

인프런 강의 - 스프링과 JPA 기반 웹 애플리케이션 개발