How can I Integrate SpringSecuirty to My SpringBootTest?

Issue

This Content is from Stack Overflow. Question asked by YSEO

I’m trying to test a comment_post method.
Comment has many – to – one relationship with User Entity which comes from Spring Security.
I connected this relationship by using Principal.
I think I made it working properly, but having trouble applying it to test.

Problem is that Comment Posting method gets user by finding User in Repository using Principal’s email attribute, So I need to apply SecurityContext to test,
but I have no idea how to apply this function to test.

By Searching, I found out that I can make SpringSecurityContext by @WithSecurityContext
annotation, so I’m trying to apply it but having this error

java.lang.RuntimeException: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springboot.web.CommentsApiControllerTest$WithUserDetailsSecurityContextFactory': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'springboot.web.CommentsApiControllerTest' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

I’m not even sure that my approach is correct.
tbh, I kind of feel lost, maybe it’s because I’m new to SpringBoot, also Security.

Here’s my codes.

CommentService

@RequiredArgsConstructor
@Service
public class CommentService {

private final CommentRepository commentRepository;
private final PostsRepository postsRepository;
private final UserDetailService userDetailService;

@Transactional
public Long commentSave(CommentSaveRequestDto requestDto, Long id) {
    Posts post = postsRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다"));
    requestDto.setPosts(post);

    User user = userDetailService.returnUser();
    requestDto.setUser(user);

    return commentRepository.save(requestDto.toEntity()).getId();
}

`

UserDetailService

@RequiredArgsConstructor
@Service
public class UserDetailService {

private final UserRepository userRepository;

public User returnUser() {
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    String userName;

    if (principal instanceof UserDetails) {
        userName = ((UserDetails) principal).getUsername();
    } else {
        userName = principal.toString();
    }

    int start = userName.indexOf("email")+6;
    int end = userName.indexOf(".com,")+4;
    String email = userName.substring(start, end);

    User user = userRepository.findByEmail(email).orElse(null);

    return user;
}

CommentSaveRequestDto

@Data
@NoArgsConstructor
@Builder
@AllArgsConstructor
public class CommentSaveRequestDto {
    private String comment;
    private Posts posts;

    private User user;

    /* Dto -> Entity */
    public Comment toEntity() {
        return Comment.builder()
                .comment(comment)
                .posts(posts)
                .user(user)
                .build();
    }
}

And here is my CommentsApiControllrTest

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Transactional
public class CommentsApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private PostsRepository postsRepository;

    @Autowired
    private CommentRepository commentRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PostsService postsService;

    @Autowired
    private CommentService commentService;

    @Autowired
    private UserDetailService userDetailsService;

    @Autowired
    private WebApplicationContext context;

    @Autowired ObjectMapper objectMapper;

    private MockMvc mvc;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .apply(sharedHttpSession())
                .build();
    }

    @Retention(RetentionPolicy.RUNTIME)
    @WithSecurityContext(factory = WithUserDetailsSecurityContextFactory.class)
    public @interface WithMockCustomUser {
        String name() default "testName";

        String email() default "testemail@gmail.com";

        Role role() default Role.USER;
    }

    final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory<WithUserDetails> {
        private final UserDetailsService userDetailsService;
        @Autowired
        public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) {
            this.userDetailsService = userDetailsService;
        }
        public org.springframework.security.core.context.SecurityContext createSecurityContext(WithUserDetails withUser) {
            String username = withUser.value();
            Assert.hasLength(username, "value() must be non-empty String");
            UserDetails principal = userDetailsService.loadUserByUsername(username);
            Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
            SecurityContext context = SecurityContextHolder.createEmptyContext();
            context.setAuthentication(authentication);
            return context;
        }
    }

    @After
    public void tearDown() throws Exception {
        postsRepository.deleteAll();
        commentRepository.deleteAll();
    }

    @Test
    @WithMockCustomUser
    @Transactional // 프록시 객체에 실제 데이터를 불러올 수 있게 영속성 컨텍스트에서 관리
    public void comment_등록() throws Exception {
        // given
        String title = "title";
        String content = "content";
        User user = userRepository.save(User.builder()
                .name("name")
                .email("fake@naver.com")
                .picture("fakePic.com")
                .role(Role.USER)
                .build());

        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .user(user)
                .build();
        postsRepository.save(requestDto.toEntity());

        String comment = "comment";
        Posts posts = postsRepository.findAll().get(0);

        CommentSaveRequestDto saveRequestDto = CommentSaveRequestDto.builder()
                .comment(comment)
                .posts(posts)
                .build();

        Long id = posts.getId();

        String url = "http://localhost:"+ port + "/api/posts/" + id + "/comments";

        //when

        mvc.perform(post(url)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content(objectMapper.writeValueAsString(saveRequestDto)))
                .andExpect(status().isOk())
                .andDo(print());

    }

All I want is to make a mock Security User in test, so that

User user = userDetailService.returnUser();

this line in CommentService don’t make any error.

Just a little tip would be really helpful to me.
Thank you in advance.



Solution

This question is not yet answered, be the first one who answer using the comment. Later the confirmed answer will be published as the solution.

This Question and Answer are collected from stackoverflow and tested by JTuto community, is licensed under the terms of CC BY-SA 2.5. - CC BY-SA 3.0. - CC BY-SA 4.0.

people found this article helpful. What about you?