Mockito.when is giving random results - spring-boot

I'm trying to test this changePassword method from my service class:
#Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public UserService(UserConverter userConverter, UserRepository userRepository,
PasswordEncoder passwordEncoder, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userConverter = userConverter;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
public User changePassword(String email, String oldPassword, String newPassword, String modifiedBy) {
User foundUser = userRepository.findByEmail(email).orElse(null);
if (Objects.isNull(foundUser)) {
String errorMessage = String.format("User lookup failed for the email '%s'", email);
logger.warn(errorMessage);
throw new EntityNotFoundException(errorMessage);
}
if (!passwordEncoder.matches(oldPassword, foundUser.getPassword())) {
String errorMessage = "Old password doesn't match";
logger.warn(errorMessage);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage);
}
foundUser.setPassword(bCryptPasswordEncoder.encode(newPassword));
foundUser.setModifiedBy(modifiedBy);
return userRepository.save(foundUser);
} // Plus other unrelated methods
And my test class is as follows:
#ExtendWith(MockitoExtension.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
#Mock
private UserConverter userConverter;
#Mock
private PasswordEncoder passwordEncoder;
#Mock
private BCryptPasswordEncoder bCryptPasswordEncoder;
#InjectMocks
private UserService userService;
#Test
public void changePasswordSuccessfully() throws Exception {
String email = "bojack#bojack.com";
String oldPassword = "Bojack1234";
String newPassword = "Bojack12345";
String modifiedBy = "peanutbutter#hollywoo.com";
User userToBeReturned = new User();
userToBeReturned.setPassword(newPassword);
userToBeReturned.setModifiedBy(modifiedBy);
doReturn(Optional.ofNullable(genericUser))
.when(userRepository).findByEmail(anyString());
doReturn(userToBeReturned)
.when(userRepository).save(any(User.class));
when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true);
when(bCryptPasswordEncoder.encode(anyString())).thenReturn(newPassword);
User returnedUser = userService.changePassword(email, oldPassword, newPassword, modifiedBy);
assertEquals(userToBeReturned.getModifiedBy(), returnedUser.getModifiedBy());
assertEquals(userToBeReturned.getPassword(), returnedUser.getPassword());
}
#Test
public void oldPasswordDoesNotMatchWhenChangingPassword() throws Exception {
String email = "bojack#bojack.com";
String oldPassword = "Bojack1234";
String newPassword = "Bojack12345";
String modifiedBy = "peanutbutter#hollywoo.com";
User userToBeReturned = new User();
userToBeReturned.setPassword(newPassword);
userToBeReturned.setModifiedBy(modifiedBy);
doReturn(Optional.ofNullable(genericUser))
.when(userRepository).findByEmail(anyString());
assertThrows(ResponseStatusException.class, () -> {
userService.changePassword(email, oldPassword, newPassword, modifiedBy);
});
} // Plus other tests unrelated to the method that I'm testing
}
And it indeed works, but randomly, I test my whole test package and sometimes all tests pass but sometimes changePasswordSuccessfully fails on this line from UserService.java because it goes inside the if, which I'm trying to avoid
if (!passwordEncoder.matches(oldPassword, foundUser.getPassword())) {
It seems as if the when(passwordEncoder.matches(anyString(), anyString())).thenReturn(true); is not being respected, I've also tried with doReturn but to no avail.
Relevant error:
org.springframework.web.server.ResponseStatusException: 400 BAD_REQUEST "Old password doesn't match"
at cr.lasbrumas.lasbrumassource.user.UserService.changePassword(UserService.java:62)
at cr.lasbrumas.lasbrumassource.user.UserServiceTest.changePasswordSuccessfully(UserServiceTest.java:128)
Also this is how I'm cresting the bean
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

I found the answer, I don't know what the formal name for this is called but apparently since I was also creating a bean of bCryptPasswordEncoder and I was also mocking it and mocking the response of bCryptPasswordEncoder.encode in the same test, it was "shadowing" the mock of PasswordEncoder, therefore it invalidated the mock for the .matches method randomly. I would guess that this happens since bCryptPasswordEncoder is an implementation of the PasswordEncoder interface.
Though there's no need for using bCryptPasswordEncoder in my case, since PasswordEncoder.encode uses bcrypt as the default.

Related

How can I Integrate SpringSecuirty to My SpringBootTest?

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.

I have a problem with testing adding user with password encoder

I have a problem when testing a method with using passwordencoder:
Cannot invoke "org.springframework.security.crypto.password.PasswordEncoder.encode(java.lang.CharSequence)" because the return value of "com.store.restAPI.user.UserConfig.passwordEncoder()" is null`
Thats my test class method:
#ExtendWith(MockitoExtension.class)
class UserServiceTest {
private UserService underTest;
#Mock
private UserRepository userRepository;
#Mock
private UserConfig userConfig;
#BeforeEach
void setUp(){
underTest = new UserService(userRepository, userConfig);
}
#Test
void itShouldFindAllUsers() {
//when
underTest.findAll();
//then
verify(userRepository).findAll();
}
#Test
void addNewUser() {
//given
User expected = new User(
"mateusz#gmail.com",
"123"
);
//when
underTest.addNewUser(expected);
//then
ArgumentCaptor<User> userArgumentCaptor =
ArgumentCaptor.forClass(User.class);
verify(userRepository).save(userArgumentCaptor.capture());
User capturedUser = userArgumentCaptor.getValue();
assertThat(capturedUser).isEqualTo(expected);
}
#Test
#Disabled
void loginUser() {
}
}
And thats UserService method that i want to test:
#Service
public class UserService {
private final UserRepository userRepository;
private final UserConfig userConfig;
#Autowired
public UserService(UserRepository userRepository, UserConfig userConfig) {
this.userRepository = userRepository;
this.userConfig = userConfig;
}
public List<User> findAll() {
return userRepository.findAll();
}
public void addNewUser(User user) {
Optional<User> userOptional = userRepository.findUserByEmail(user.getEmail());
if(userOptional.isPresent()){
throw new IllegalStateException("email taken");
}
String hashedPassword = userConfig.passwordEncoder().encode(user.getPassword());
user.setPassword(hashedPassword);
userRepository.save(user);
}
public void loginUser(User user){
Optional<User> userOptional = userRepository.findUserByEmail(user.getEmail());
if(userOptional.isEmpty()){
throw new IllegalStateException("no account under that email");
}
else
if(!userConfig.passwordEncoder().matches(user.getPassword(),
userOptional.get().getPassword())){
throw new IllegalStateException("wrong password");
}
//!userOptional.get().getPassword().equals(user.getPassword())
}
}
Password encoder is a bean in class UserConfig.
#Configuration
public class UserConfig {
#Bean
CommandLineRunner commandLineRunnerUser(UserRepository repository) {
return args -> {
User iza = new User(
"iza#gmail.com",
"$2a$10$U87IFlm9DYXRITUSnfdfDuknz8ijJCcK9UVR4D4kUDu7w13zPuURK"
);
User andrzej = new User(
"andrzej#gmail.com",
"$2a$10$fmYOxyvWBr47wAg1m/ryy.G4J1PbT2LRj6m7oENkBtEsGocansE9G"
);
User tomek = new User(
"tomek#gmail.com",
"$2a$10$chrySvbZSZcje4r3Q0PZv.FrO6/k2WvM42GX3x2EmySZc/dAA2glC"
);
repository.saveAll(
List.of(iza,andrzej,tomek)
);
};
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Do i need to create another method with password encoder inside my test class? I don't know what am i doing wrong. Why does it say that result is null?
Does someone know what am i doing wrong?
In your UserServiceTest, you're currently mocking UserConfig. A mock is basically an implementation that has no behavior at all. This means that userConfig.passwordEncoder() will return null in your test.
To solve this, you have to tell Mockito what the behavior is of your UserConfig class. For example:
Mockito.when(userConfig.passwordEncoder()).thenReturn(new BCryptPasswordEncoder());
This also allows you to use a different (eg. a dummy) password encoder in your unit tests.
Another suggestion, unrelated to your question, is that you can directly autowire PasswordEncoder in your UserService in stead of autowiring UserConfig. This works because you annotated UserConfig with #Configuration and UserConfig.passwordEncoder() with #Bean:
#Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder; // Change this
// And change the constructor parameter
#Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder; // And this
}
// The rest of your code...
}

Springboot does not save user properly using JpaRepository

I couldn't use userRepo to saveUser properly, the mysql query showed wrong order of insertion, therefore not working. Here's my UserRepository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findUserByUsername(String username);
}
Here's my user service:
#Service
public class UserServiceImpl implements IUserService, UserDetailsService {
private Logger logger = LogManager.getLogger(UserServiceImpl.class);
#Autowired
private UserRepository userRepo;
#Autowired
private BCryptPasswordEncoder encoder;
//field name not in correct order, can not use it
#Override
public Integer saveUser(User user) {
String passwd = user.getPassword();
String encodedPasswod = encoder.encode(passwd);
logger.log(Level.ERROR, "passwd:" + encodedPasswod);
user.setPassword(encodedPasswod);
ArrayList<String> roles = new ArrayList<String>();
roles.add("Member");
user.setRoles(roles);
user = userRepo.save(user);
return user.getUserid();
}
public UserServiceImpl(UserRepository userRepository) {
this.userRepo = userRepository;
}
...
Someone can help? So frustrated with springboot.
M.
Look like you missing #Transactional on the saveUser method.

JHipster: Receive 401 Unauthorized when testing microservices

I generated simple microservices application with Jhipster I wrote simple controller like hello world,
When I am trying to test through test method it is always giving Unauthorized error and the test fails.
Controller:
#RestController
#RequestMapping("/api")
public class TestController{
#GetMapping("/test/{Id}")
public String TestGetData(#PathVariable int Id) {
return "Here is your data!";
}
}
Testclass:
#SpringBootTest(classes = HerstellerController.class)
#AutoConfigureMockMvc
public class TestIT {
#Autowired
private MockMvc mockMvc;
private static final long ONE_MINUTE = 60000;
private String token;
private Key key;
private TokenProvider tokenProvider;
#BeforeEach
public void setup() {
tokenProvider = new TokenProvider( new JHipsterProperties());
key = Keys.hmacShaKeyFor(Decoders.BASE64
.decode("xxxx"));
ReflectionTestUtils.setField(tokenProvider, "key", key);
ReflectionTestUtils.setField(tokenProvider, "tokenValidityInMilliseconds", ONE_MINUTE);
}
#Test
public void TestData() throws Exception {
token=tokenProvider.createToken(createAuthentication(),false);
String id="1";
String expData = "Here is your data!";
String result = mockMvc.perform(get("/api/test/"+ id)
.header("Authorization","Bearer " + token))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
System.out.println("\nResult:\n"+result);
}
private Authentication createAuthentication() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ADMIN));
return new UsernamePasswordAuthenticationToken("admin", "admin", authorities);
}
Changed the securityconfig also like this
.antMatchers("/api/**").permitAll()
.antMatchers("/api/**").anonymous()
I added authentication to securityContext in setup of Testclass!, It works!
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(createAuthentication());
SecurityContextHolder.setContext(securityContext);

Spring controller testing with Mockito

I'm trying to test My Spring controllers using Mockito, but I can't actually get how can I do that without making everything #Mock.
Moreover test method returns me NullPointerException, as it can see no user and actually no user role at all.
Is there a way to test my controllers somehow?
(Controller class)
#Controller
#SessionAttributes("user")
#RequestMapping("/login.htm")
public class LoginController{
#Autowired
private UserDao userDao;
#Autowired
private LoginValidator loginValidator;
public LoginValidator getLoginValidator() {
return loginValidator;
}
public void setLoginValidator(LoginValidator loginValidator) {
this.loginValidator = loginValidator;
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
#RequestMapping(method = RequestMethod.GET)
public String getSendEmptyForm(ModelMap model, HttpServletRequest req) {
req.getSession().invalidate();
model.addAttribute("loginForm", new LoginForm());
return "login";
}
#RequestMapping(method = RequestMethod.POST)
public String postSubmittedForm(ModelMap model, #ModelAttribute("loginForm") LoginForm loginForm,
BindingResult result, SessionStatus status) {
//validate form
loginValidator.validate(loginForm, result);
if (result.hasErrors()) {
return "login";
}
User user = userDao.findByLogin(loginForm.getLogin());
model.addAttribute("user", user);
if (user.getRole().getName().equals("Admin")) {
model.addAttribute("usersList", userDao.findAll());
return "viewAllUsersPage";
}
if (user.getRole().getName().equals("User")){
return "userPage";
}
model.addAttribute("error", "Your role is not User or Admin");
return "errorPage";
}
}
And my testing class
#RunWith(MockitoJUnitRunner.class)
public class LoginControllerTest {
#InjectMocks
private LoginController controllerUT = new LoginController();
#Mock
private ModelMap model;
#Mock
private LoginForm loginForm;
#Mock
private BindingResult result;
#Mock
private SessionStatus status;
#Mock
private LoginValidator validator;
#Mock
private UserDao userDao;
#Mock
private User useк;
#Test
public void testSendRedirect(){
final String target = "login";
String nextPage = controllerUT.postSubmittedForm(model, loginForm, result, status);
assertEquals(target, nextPage);
}
}
First off you seem to be missing stubbing for loginForm.getLogin() and userDao.findByLogin(loginForm.getLogin()) and user.getRole().getName(). Without such stubbing, these methods called on a mock will return a default value (i.e. null).
So you may want to add :
when(loginForm.getLogin()).thenReturn(login);
when(userDao.findByLogin(login)).thenReturn(user);
when(user.getRole()).thenReturn(role);
when(role.getName()).thenReturn("Admin");
You will want to vary the return values for different tests.
Depending on your implementation classes for User and Role, you could simply supply real instances.
For a test that simulates the result to have errors you'll want to add this stubbing :
when(result.hasErrors()).thenReturn(true);
since otherwise the default false is returned.

Resources