본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/1부 (개발환경, 회원가입, 로그인, 계정설정)

24. 프로필 수정 테스트

by Backchus 2020. 4. 21.

인증된 사용자가 접근할 수 있는 기능 테스트하기

  • 실제 DB에 저장되어 있는 정보에 대응하는 인증된 Authentication에 필요하다.
  • @WithMockUser로는 처리할 수 없다.(버그)

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

  • @WithAccount

커스텀 애노테이션 생성

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

    String value();
}

 

SecurityContextFactory 구현

package me.weekbelt.studyolle;

@RequiredArgsConstructor
public class WithAccountSecurityContextFactory implements WithSecurityContextFactory<WithAccount> {

    private final AccountService accountService;

    // @WithUserDetails(value = "joohyuk", setupBefore = TestExecutionEvent.TEST_EXECUTION)가 원래 하는일
    @Override
    public SecurityContext createSecurityContext(WithAccount withAccount) {
        String nickname = withAccount.value();

        // 회원 가입
        SignUpForm signUpForm = new SignUpForm();
        signUpForm.setNickname(nickname);
        signUpForm.setEmail(nickname + "@hanmail.net");
        signUpForm.setPassword("12345678");
        accountService.processNewAccount(signUpForm);

        // Authentication 만들고 SecurityContext에 넣어주기
        UserDetails principal = accountService.loadUserByUsername(nickname);
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);

        return context;
    }
}

 

테스트 코드 작성

package me.weekbelt.studyolle.settings;

@SpringBootTest
@AutoConfigureMockMvc
class SettingsControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Autowired
    AccountRepository accountRepository;

    @AfterEach
    public void afterEach() {
        accountRepository.deleteAll();;
    }

    @WithAccount("joohyuk")
    @DisplayName("프로필 수정 폼")
    @Test
    public void updateProfileForm() throws Exception {
        String bio = "짧은 소개를 수정하는 경우";
        mockMvc.perform(get("/settings/profile"))
                .andExpect(status().isOk())
                .andExpect(model().attributeExists("account"))
                .andExpect(model().attributeExists("profile"));
    }

    @WithAccount("joohyuk")
    @DisplayName("프로필 수정 하기 - 입력값 정상")
    @Test
    public void updateProfile() throws Exception {
        String bio = "짧은 소개를 수정하는 경우";
        mockMvc.perform(post("/settings/profile")
                .param("bio", bio)
                .with(csrf()))
                .andExpect(status().is3xxRedirection())
                .andExpect(redirectedUrl("/settings/profile"))
                .andExpect(flash().attributeExists("message"))
        ;
        Account joohyuk = accountRepository.findByNickname("joohyuk");
        assertThat(joohyuk.getBio()).isEqualTo(bio);
    }

    @WithAccount("joohyuk")
    @DisplayName("프로필 수정 하기 - 입력값 에러")
    @Test
    public void updateProfile_error() throws Exception {
        String bio = "길게 소개를 수정하는 경우, 길게 소개를 수정하는 경우, 길게 소개를 수정하는 경우, 길게 소개를 수정하는 경우";
        mockMvc.perform(post("/settings/profile")
                .param("bio", bio)
                .with(csrf()))
                .andExpect(status().isOk())
                .andExpect(view().name("settings/profile"))
                .andExpect(model().attributeExists("account"))
                .andExpect(model().attributeExists("profile"))
                .andExpect(model().hasErrors())
        ;

        Account joohyuk = accountRepository.findByNickname("joohyuk");
        assertThat(joohyuk.getBio()).isNull();
    }
}

 

프로필 수정 테스트를 하기 위해서 기본적으로 계정에 대한 정보가 있어야 한다. 

그래서 테스트 클래스에 @WithMockUser를 주면 Authentication안에 UsernamePasswordAuthenticationToken타입으로 username을 "user", password를 "password"를 넣어주는데 쓸 수 없다. 우리는 진짜 DB에 저장된 데이터가 SecurityContext에 들어 있어야 한다.

@WithUserDetails("username")을 쓸 수 있다. 우리가 만든 UserDetailsService를 사용해서 username에 해당하는 데이터를 가지고 테스트를 실행하게 된다. 그래서 테스트 시작 전에 @BeforeEach를 이용해 계정 정보를 저장하고 실행하면 테스트가 작동할 것 같지만 하지 않는다. 왜냐하면 @BeforeEach가 실행하기 전에 @WithUSerDetails("username")이 먼저 실행하기 때문이다. 그래서 @BeforeEach가 실행 후와 테스트 코드 직전에 실행하기 위해 

@WithUserDetails(value = "joohyuk", setupBefore = TestExecutionEvent.TEST_EXECUTION)

이렇게 사용하게 되는데 버그가 있다. 따라서 위처럼 Custom한 Annotation을 작성하고 SecurityContextFactory를 따로 구현하여 계정 정보를 활성화한다.

 

참조: https://docs.spring.io/spring-security/site/docs/current/reference/html5/

 

Spring Security Reference

In Spring Security 3.0, the codebase was sub-divided into separate jars which more clearly separate different functionality areas and third-party dependencies. If you use Maven to build your project, these are the modules you should add to your pom.xml. Ev

docs.spring.io

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

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

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습니다. 이 강좌를 충분히 학습한다면 여러분 만의 웹 서비스를 만들거나 취직에 도움이 될만한 포트폴리오를 만들 수 있을 겁니다. 활용 웹 개발 프레임워크 및 라이브러리 Java Spring Spring Boot Spring Data JPA Thymeleaf 온라인 강의 스

www.inflearn.com