Reputation: 1141
I'm trying to unit test a Spring-boot controller and one of my @Autowired
fields is coming back null.
I have two autowired fields in this controller:
public class UserProfileController{
@Autowired
private UserProfileService profileService;
@Autowired
private IDataValidator dataValidatorImpl;
My test class is as follows:
@RunWith(SpringJUnit4ClassRunner.class)
@WebIntegrationTest
@SpringApplicationConfiguration(classes = UserProfileServiceApplication.class)
public class ControllerTest {
private MockMvc mockMvc;
@Mock
UserProfileService profileServiceMock;
@Autowired
ApplicationContext actx;
@InjectMocks
private UserProfileController profileController;
@Before
public void setup() {
// Process mock annotations
String[] asdf = actx.getBeanDefinitionNames();
for (int i = 0; i < asdf.length; i++){
System.out.println(asdf[i]);
}
MockitoAnnotations.initMocks(this);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(profileController).build();
}
/**
* All this does is verify that we return the correct datatype and HTTP status
* @throws Exception
*/
@Test
public void testGetProfileSuccess() throws Exception {
Mockito.when(profileServiceMock.getProfile(Mockito.any(HashMap.class))).thenReturn(new HashMap<String, Object>());
mockMvc.perform(get("http://localhost:8095/UserName?tenantId=tenant1"))
.andExpect(status().isOk())
.andExpect(content().contentType(TestUtil.APPLICATION_JSON_UTF8));
//verify profileService was only used once
Mockito.verify(profileServiceMock, Mockito.times(1)).getProfile(Mockito.any(HashMap.class));
//verify we're done interacting with profile service
Mockito.verifyNoMoreInteractions(profileServiceMock);
}
If I leave IDataValidator untouched in the test class, it comes up null and I get a NPE. If I @Spy
the DataValidatorImpl, it cannot find properties from the Spring environment that it needs to work.
How can I just let the IDataValidator autowire itself and maintain its spring environment context as if I were just running the application normally?
When I print all beans in my @Before
setup() method, I can see DataValidationImpl in the list.
Upvotes: 1
Views: 1480
Reputation: 116
When you mock your controller with
MockMvcBuilders.standaloneSetup(profileController).build();
the controller is replaced in the context. Since you did not inject any IDataValidator
in it, it is null.
The simplest solution is to autowired the real IDataValidator
into your test class and inject it into the controller.
In your controller:
public class UserProfileController{
private UserProfileService profileService;
private IDataValidator dataValidatorImpl;
@Autowired
public UserProfileController(UserProfileService profileService, IDataValidator dataValidatorImpl) {
this.profileService = profileService;
this.dataValidatorImpl = dataValidatorImpl;
}
And in your test :
@RunWith(SpringJUnit4ClassRunner.class)
@WebIntegrationTest
@SpringApplicationConfiguration(classes = UserProfileServiceApplication.class)
public class ControllerTest {
private MockMvc mockMvc;
private UserProfileService profileService;
@Autowired
private IDataValidator dataValidator;
@Before
public void setup() {
UserProfileService profileService = Mockito.mock(UserProfileService.class);
UserProfileController controller = new UserProfileController(profileService, dataValidator);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
}
Upvotes: 1
Reputation: 1141
Okay, I swear I did this the first time but when trying to recreate the error thrown for jny it actually worked.
My solution is to inject via @Spy
annotation and get the bean from the ApplicationContext
in my @Before
setup method.
public class ControllerTest {
private MockMvc mockMvc;
@Mock
UserProfileService profileServiceMock;
@Spy
IDataValidator dataValidator;
@Autowired
ApplicationContext actx;
@InjectMocks
private UserProfileController profileController;
@Before
public void setup() {
dataValidator = (IDataValidator) actx.getBean("dataValidatorImpl");
MockitoAnnotations.initMocks(this);
// Setup Spring test in standalone mode
this.mockMvc = MockMvcBuilders.standaloneSetup(profileController).build();
}
Upvotes: 0
Reputation: 8057
If I understand correctly, you want to inject UserProfileController
with real Validator and mock Service.
In this case I suggest to use @ContextConfiguration
annotaion which allows to configure context in the test. You'll need to create a Configuration class:
@RunWith(SpringJUnit4ClassRunner.class)
@WebIntegrationTest
@SpringApplicationConfiguration(classes = UserProfileServiceApplication.class)
public class ControllerTest {
private MockMvc mockMvc;
@Mock
UserProfileService profileServiceMock;
@Autowired
ApplicationContext actx;
//comment this line out
//@InjectMocks
@Autowired
private UserProfileController profileController;
@Before
public void setup() {
// Process mock annotations
String[] asdf = actx.getBeanDefinitionNames();
for (int i = 0; i < asdf.length; i++){
System.out.println(asdf[i]);
}
//comment this line out
//MockitoAnnotations.initMocks(this);
@Configuration
public static class Config {
//wire validator - if it is not wired by other configurations
@Bean
Validator validator() {
return new Validaor();
}
//wire mock service
@Bean
public UserProfileService profileService() {
return mock(UserProfileService.class);
}
}
Upvotes: 0