lp1776
lp1776

Reputation: 1141

How do I autowire a field with a real implementation while mocking another field?

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

Answers (3)

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

lp1776
lp1776

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

jny
jny

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

Related Questions