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 "[email protected]";
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("[email protected]")
.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.