Issue
I´m trying to make a Unit test of the methods of my project which contains spring security. When I run the project it works normally, but when I try to unit test the methods it gives me this error.
Description:
Pramenter 0 of constructor in ...config.secutityConfig required a bean of type 'org.springframeword.security.userdetails.UserDetailsService' that could not be found
Action:
Consider defining a bean of type 'org.springframework.security.core.userdetails.UserDetailsService in your configuration'
This is my SecurityConfig.java code:
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers(GET, "/user/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(POST, "/user/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(PUT, "/user/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(DELETE, "/user/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(GET, "/category/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(POST, "/category/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(PUT, "/category/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(DELETE, "/category/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(GET, "/product/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(POST, "/product/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(PUT, "/product/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(DELETE, "/product/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(GET, "/shoppingcart/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(POST, "/shoppingcart/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(PUT, "/shoppingcart/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(DELETE, "/shoppingcart/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers(GET, "/product/**").hasAnyAuthority("USER");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(customAuthenticationFilter);
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception{
return super.authenticationManagerBean();
}
}
This is the test I'm trying to make, it's basically to save a category in the DB.
@WebMvcTest(controllers = CategoryRest.class)
public class CategoryRestTest extends AbstractUnitRestTest {
@MockBean
private CategoryService categoryService;
@Test
public void saveCategory() throws Exception {
CreateCategoryCmd cmd = new CreateCategoryCmd("Tehnika", "TV, USB", Collections.emptySet());
String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(cmd);
Category category = CategoryBuilder.categoryBelaTehnika();
doReturn(category).when(categoryService).save(any(CreateCategoryCmd.class));
mockMvc.perform(post("/category/save")
.contentType(MediaType.APPLICATION_JSON)
.content(jsonInString)).andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value(category.getName()));
}
And this is my Userdetails:
@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl implements UserService, UserDetailsService {
private final static Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
private final UserDAO userDAO;
private final PayPalAccountDAO payPalAccountDAO;
private final RoleDao roleDao;
private final ShoppingCartDAO shoppingCartDAO;
private final PasswordEncoder passwordEncoder;
@Override
@Transactional
public User save(CreateUserCmd cmd) throws ServiceException {
User user = UserMapper.INSTANCE.createUserCmdToUser(cmd);
List<Role> roles = new ArrayList<>();
List<Role> role = new ArrayList<>();
Set<Role> ro = new HashSet<>();
roles = roleDao.findAll();
role.add(roles.get(0));
ro.addAll(role);
try {
user.setRoles(ro);
user.setPassword(passwordEncoder.encode(cmd.getPassword()));
user = userDAO.save(user);
} catch (DAOException e) {
LOGGER.error(null, e);
throw new ServiceException(ErrorCode.ERR_GEN_001, "Saving of user failed!", e);
}
return user;
}
@Override
public List<UserResult> findAll() {
return UserMapper.INSTANCE.listUserToListUserResult(userDAO.findAll());
}
@Override
public UserInfo findById(Long id) {
return UserMapper.INSTANCE.userToUserInfo(userDAO.findOne(id));
}
@Override
public void addAccount(PayPalAccount payPalAccount, User user) throws ServiceException{
try{
payPalAccount.setUserID(user);
payPalAccountDAO.save(payPalAccount);
} catch (DAOException e){
LOGGER.error(null, e);
throw new ServiceException(ErrorCode.ERR_GEN_001, "creating account failed");
}
}
@Override
public void addCart(ShoppingCart shoppingCart, User user) throws ServiceException {
try{
shoppingCart.setUser(user);
shoppingCart.setStatus(Status.NEW);
shoppingCart.setPrice(new BigDecimal(0));
shoppingCartDAO.save(shoppingCart);
} catch (DAOException e) {
LOGGER.error(null, e);
throw new ServiceException(ErrorCode.ERR_GEN_001, "creating cart failed ");
}
}
@Override
public void addRole(addRoleCmd cmd) throws ServiceException {
User user;
try{
user = userDAO.findOne(cmd.getId());
if(user == null){
throw new ServiceException(ErrorCode.ERR_GEN_002);
}
UserMapper.INSTANCE.addingRoletoUser(user, cmd);
user.getRoles().addAll(cmd.getRoles().stream()
.map(v ->{
Role rr = roleDao.findOne(v.getId());
rr.getUser().add(user);
return rr;
}).collect(Collectors.toSet()));
userDAO.merge(user);
}catch (DAOException e){
LOGGER.error(null, e);
throw new ServiceException(ErrorCode.ERR_GEN_001, "failed while adding new role", e);
}
}
@Override
public void update(UpdateUserCmd cmd) throws ServiceException {
User user;
try {
// check if entity still exists
user = userDAO.findOne(cmd.getId());
if (user == null) {
throw new ServiceException(ErrorCode.ERR_GEN_002);
}
UserMapper.INSTANCE.updateUserCmdToUser(user, cmd);
PayPalAccount palAccount = cmd.getPayPalAccount();
Set<ShoppingCart> shoppingCarts = cmd.getShoppingCarts();
for (ShoppingCart cart: shoppingCarts) {
addCart(cart, user);
}
user.setAccount(palAccount);
addAccount(palAccount, user);
userDAO.merge(user);
} catch (DAOException e) {
LOGGER.error(null, e);
throw new ServiceException(ErrorCode.ERR_GEN_001, "Update of user failed!", e);
}
}
@Override
public void delete(Long id) throws ServiceException {
User user = userDAO.findOne(id);
if (user != null) {
try {
userDAO.delete(user);
} catch (DAOException e) {
LOGGER.error(null, e);
throw new ServiceException(ErrorCode.ERR_GEN_001, "Delete of user failed!", e);
}
} else {
throw new ServiceException(ErrorCode.ERR_CAT_001, "User does not exist!");
}
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDAO.findByUsername(username);
if(user == null){
LOGGER.error("User not found");
throw new UsernameNotFoundException("User not found in the database");
} else{
LOGGER.info("User found in the DB");
}
Collection<SimpleGrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getName()));
});
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
}
Any suggestions?
Solution
The problem here is that if you read the documentation for WebMvcTest
it says straight out in the second paragraph:
Using this annotation will disable full auto-configuration and instead apply only configuration relevant to MVC tests (i.e. @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer and HandlerMethodArgumentResolver beans
but not @Component, @Service or @Repository beans)
.
Which means it will only load a subsection of the application.
The code provided shows
@WebMvcTest(controllers = CategoryRest.class)
Which will only load the defined controller and the rest defined in the documentation.
The UserDetailsService
is annotated as a @Service
which means it will NOT be loaded at startup.
If you want to load the application fully you need to use @SpringBootTest
in conjuction with other annotations for instance @AutoConfigureMockMvc
or @AutoConfigureWebTestClient
depending on which client to use.
All of this is properly documentated with easy to read instructions in the spring boot documentation testing chapter.
Answered By - Toerktumlare
Answer Checked By - Clifford M. (JavaFixing Volunteer)