Problem autowiring UserDetailsService while running tests - java

I added spring security to my application and it's working great, but now existing tests are broken, when I run them I get:
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userDetailsService in xxx.xxx.someapp.spring.security.WebSecurityConfig required a bean of type 'xxx.xxx.sardinatank.someapp.security.services.UserDetailsServiceImpl' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
This is how I'm wiring the UserDetailsServiceImpl in my WebSecurityConfig
#Autowired
UserDetailsServiceImpl userDetailsService;
This is an example controller
#RestController
public class HelloWorldController {
#GetMapping
public ModelAndView helloWorld() {
final ModelAndView bootstrapFront = new ModelAndView("index.html");
return bootstrapFront;
}
}
And this is a test that fails
#WebMvcTest(controllers = HelloWorldController.class)
#ContextConfiguration
#WebAppConfiguration
class HelloWorldControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void getBootstrappedDoc() throws Exception {
MvcResult mvcResult = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andReturn();
assertEquals("index.html", mvcResult.getModelAndView().getViewName());
}
}

I guess you might have declared it as a #Component or something that is not part of this list:
#Controller, #ControllerAdvice, #JsonComponent, Converter, Filter, WebMvcConfigurer
which means UserdetailServiceImpl will not be part of the #SpringWebMVC sliced test context, hence spring doesn't bootstrap it.
You have 2 options:
Either you decorate your UserServiceImpl with one of the aforementioned annotations: e.g
#JsonComponent
class UserDetailsServiceImpl implements userDetailsService { ... };
Or you inject a mock of UserServiceImpl in your test with #MockBean:
#WebMvcTest(controllers = HelloWorldController.class)
#ContextConfiguration
#WebAppConfiguration
class HelloWorldControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
UserDetailsServiceImpl userDetailsService;
private MockMvc mockMvc;
//...
}

Related

Difference between Import/Autowire/MockBean/ContextConfiguration in Spring test

I frequently see Spring test like this:
#WebMvcTest(controllers = MyController.class)
#Import({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, JpaConfig.class, MyService.class})
#ContextConfiguration(classes = AppConfig.class)
#MockBeans({#MockBean(MyLocationProvider.class), #MockBean(MyOtherService.class)})
public class MyServiceControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper objectMapper;
#Autowired
private MyJobRepository myJobRepository;
#MockBean
private MyComplicatedService myComplicatedService;
....
}
I can see there are #Import, #ContextConfiguration, #MockBeans(Before the class), #MockBeans(As fields), #Autowired.
I can understand the different between #MockBeans(Before the class), #MockBeans(As fields), #Autowired. But I am not sure what is the difference between #Import and #ContextConfiguration.
Can someone help?

WebMvcTest with real service implementation

how can I create a "quasi" MVC integration test in Spring Boot. I would like to use my real implementation of a service, but I can't mange to do it. How can I inject real implementation instead of a mock.
My classes look like this
#Controller
#RequiredArgsConstructor
public class DashboardController {
private final RolesManagerService rolesManagerService;
private final ServletRequestManagerService servletRequestManagerService;
#GetMapping({"/", "/dashboard"})
public String index(Model model, HttpServletRequest httpServletRequest) {
model.addAttribute("canAddNewOrder", rolesManagerService.canRoleAccessApplicationPart(servletRequestManagerService.getRole(httpServletRequest), ApplicationPart.CREATE_NEW_ORDER));
model.addAttribute("var", "test");
return "dashboard";
}
}
and the test
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = DashboardController.class)
#AutoConfigureMockMvc
class IndexControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private UserDetailsService userDetailsService;
#MockBean
RolesManagerService rolesManagerService;
#MockBean
private ServletRequestManagerService servletRequestManagerService;
#Test
void testDashboard() throws Exception {
mockMvc.perform(get("/dashboard").with(user("admin").password("pass").roles("USER","ADMIN")))
.andExpect(status().isOk())
.andExpect(view().name("dashboard"))
.andExpect(xpath("//a").nodeCount(1))
.andExpect(model().attributeExists("canAddNewOrder"))
.andExpect(model().size(2))
.andExpect(model().attribute("var", equalTo("test")))
.andExpect(model().attribute("canAddNewOrder", equalTo(false)))
.andDo(print());
}
}
Generally WebMvcTest will not create a full Spring context with all components (Service etc) injected, but only the Controller you define. Either use a full SpringBootTest, or add something like this in your WebMvcTest class:
#TestConfiguration
static class AdditionalTestConfig {
#Bean
public RolesManagerService getService() {
return new RolesManagerService();
}
}

Spring #WebMvcTest with Spring Security

I have a problem when I want to test controllers using #WebMvcTest - on application load I get:
...
Field customUserDetailsService in com.test.config.SecurityConfig required a bean of type 'com.test.security.CustomUserDetailsService' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.test.security.CustomUserDetailsService' in your configuration.
...
Do you have any idea what is problem? Is it good solution to exclude security?
There are classes:
#RestController
#AllArgsConstructor
public class EmployeeController {
private final EmployeeService employeeService;
#GetMapping
public List<EmployeeDTO> getEmployees() {
return employeeService.getAllEmployees();
}
}
#WebMvcTest(controllers = EmployeeController.class)
class EmployeeControllerTest {
#Autowired
private MockMvc mockMvc;
#InjectMocks
private EmployeeController employeeController;
#Test
void getEmployees_Success() throws Exception {
// mockMvc.perform(get("/employee/all")).andExpect(status().isOk());
}
The controller which you are using in test have a dependency EmployeeService employeeService;
So in test case add below.
#Mock
EmployeeService employeeService;

Spring Boot Aspectj test with MockMvc

I have a Spring boot code with Aspectj. This code has written with basic MVC architecture. Then I just try to test it with MockMVC. But when I try to test it, Aspectj doesn't interrupted. Is there a special configuration about Aspectj?
Controller:
#GetMapping("user/{userId}/todo-list")
public ResponseEntity<?> getWaitingItems(#RequestUser CurrentUser currentUser){
...handle it with service method.
}
Aspect:
#Pointcut("execution(* *(.., #RequestUser (*), ..))")
void annotatedMethod()
{
}
#Before("annotatedMethod() && #annotation(requestUser)")
public void adviseAnnotatedMethods(JoinPoint joinPoint, RequestUser requestUser)
{
...
}
Test:
#WebMvcTest(value = {Controller.class, Aspect.class})
#ActiveProfiles("test")
#ContextConfiguration(classes = {Controller.class, Aspect.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class ControllerTest
{
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private Controller controller;
#MockBean
private Service service;
#Before
public void setUp()
{
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void getWaitingItems() throws Exception
{
mockMvc.perform(get("/user/{userId}/todo-list", 1L))
.andExpect(status().isOk());
}
}
There is no need for a #SpringBootTest if you wanna do integration tests of specific controller (web layer) + your custom Aspect logic (AOP layer).
Try something like this
#WebMvcTest(controllers = {AnyController.class})
#Import({AopAutoConfiguration.class, ExceptionAspect.class})
public class ErrorControllerAdviceTest {
AnyController.class: controller under test
AopAutoConfiguration.class: Spring Boot auto-configuration of AOP
ExceptionAspect.class: class containing AOP logic
#Aspect
#Component
public class ExceptionAspect {}
Tested with Spring Boot 2.2.1.RELEASE and JUNIT5.
I am unsure, if my solution is technically the same like #Deadpool answers
Spring #WebMvcTest will only instantiate web layer and it will not load complete application context
However, in this test, Spring Boot instantiates only the web layer rather than the whole context.
In order to test Aspectj you need to load whole application context using #SpringBootTest annotation
The #SpringBootTest annotation tells Spring Boot to look for a main configuration class (one with #SpringBootApplication, for instance) and use that to start a Spring application context
So annotate the test using #SpringBootTest annotation
#SpringBootTest
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#AutoConfigureMockMvc
public class ControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private Controller controller;
#Before
public void setUp() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webApplicationContext)
.build();
}
#Test
public void getWaitingItems() throws Exception {
mockMvc.perform(get("/user/{userId}/todo-list", 1L))
.andExpect(status().isOk());
}
}

Spring MVC Test failing

I am trying to run a Spring MVC Test but keep getting this exception.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException
The exception is occuring because the autowired dependency,
#Autowired
private AccountService accountService;
is not getting injected in the test (works fine outside of the test).
Can anyone help me with this. Here is my code:
//AccountControllerITest Class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MockServletContext.class)
#WebAppConfiguration
public class AccountControllerITest {
private MockMvc mvc;
ObjectMapper om;
#Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
#Test
public void getAccounts() throws Exception {
MvcResult mvcResult = mvc.perform(MockMvcRequestBuilders.get("/api/accounts"))
.andExpect(status().isOk())
.andReturn();
}
}
}
//AccountController
#RestController
#RequestMapping("/api/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Set<AccountInfo>> getAccounts(#RequestParam(value = "firstName", required = false) String firstName,
#RequestParam(value = "surName", required = false) String surName) {
Set<AccountInfo> accounts = accountService.getAccounts(firstName, surName);
return new ResponseEntity<>(accounts, HttpStatus.OK);
}
}
Thanks for the help!
Because you are using standalone setup: mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();. If you create controller via new AccountController(), Spring doesn't have a chance to inject accountService as it doesn't control instances creation and wiring.
You have two options:
Switch your test to unit test and do not use SpringJUnit4ClassRunner nor MockServletContext at
all. You can use #InjectMocks to inject private accountService:
public class AccountControllerITest {
private MockMvc mvc;
ObjectMapper om;
#Mock
private AccountController accountController = new AccountController();
#InjectMocks
private AccountService accountService = new Mockito.mock(AccountService.class);
#Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(accountController).build();
}
There is also enhancement you can apply to your controller. Replace field injection with constructor injection and you can pass accountService to controller via constructor in test. This way you don't need to use #InjectMocks at all.
Use webAppContext setup:
#Autowired
private WebApplicationContext webApplicationContext;
#BeforeMethod
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
You need to configure your test to inject your autowired properties. See the following code block:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class AccountControllerITest {
// ... your test code
#Configuration
public static class YourConfiguration {
#Bean
AccountService accountService() {
// init and return your AccountService
}
}
}
You can init your AccountService with Mockito.

Categories

Resources