vijay sidharth
vijay sidharth

Reputation: 83

how to mock autowire fields using mockito

Here I am trying to mock autowire fields ServiceHelper of Service class TestServiceImpl , I am not able to call method through mock object of ServiceHelper class.

This is my class files:

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private TestDAO testDAO;
    @Autowired
    private ServiceHelper serviceHelper;

    @Override
    public ResultsModel getResults(Map<String, Object> map) throws WebServiceException_Exception  {     
        return serviceHelper.getResults(map);
    }

2nd Class:

@Repository
public class ServiceHelper {

    private static Logger logger = Logger.getLogger(ServiceHelper.class.getName());

    @Autowired
    ResponseHeader responseHeader;

    public void setResponseHeader(ResponseHeader responseHeader) {
        this.responseHeader = responseHeader;
    }

    public ResultsModel getResults(Map<String, Object> map) throws WebServiceException_Exception {
        ....
        }

And Test class:

@RunWith(MockitoJUnitRunner.class)
public class MockitoTester {

    @InjectMocks
    private TestServiceImpl serviceImpl = new TestServiceImpl();

    @Mock
    private TestDAO testDAO;

    @Mock
    private ServiceHelper sHelper;

    @Before
    public void initMocks(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testResult() throws Exception {
        Map<String, Object> map = new HashMap<>();
        map.put("TestId", "test123");
        map.put("lang", "en");
        map.put("cntry", "USA");
        ResultsModel results = new ResultsModel();

        when(sHelper.getResults(map)).thenReturn(results);
        results = serviceImpl.getResults(map);

        Assert.assertEquals(results.getStatus(), "Success");
    }

Here in my test class:

results = serviceImpl.getResults(map); 

It goes to TestServiceImpl class to method :

public ResultsModel getResults(Map<String, Object> map) throws webServiceException_Exception  {     
        return serviceHelper.getResults(map);
    } 

but at point :

serviceHelper.getResults(map); 

it is not going inside serviceHelper.getResults(map) and return all values as Null.

Please suggest where I need to do changes.

Upvotes: 8

Views: 28980

Answers (3)

slim
slim

Reputation: 41271

You have three choices here:

  1. Do actual Spring autowiring in your tests
  2. Use injection methods that can legitimately be performed by your tests (constructor parameters, public setters, public fields - in order of preference)
  3. Use reflection to inject your mocks

Option 1 is really integration testing -- you can annotate your test class with @RunWith(SpringRunner.class) and use more Spring annotations to control dependency injection. It's too big a subject to cover in a SO answer, but there are plenty of examples if you Google for "spring integration test mockito".


But for unit testing, I think it's better not to involve Spring. A good Spring bean doesn't need Spring to function. Option 2 just says, write your class so that unit tests (and anything else) can inject the dependency (be it a mock, or anything else) through normal Java means.

Constructor injection is cleanest in my opinion:

private final ServiceHelper serviceHelper; // note: not annotated

@Autowired
public TestService(ServiceHelper serviceHelper) {
    this.serviceHelper = serviceHelper;
}

But you can also do this with a public void setServiceHelper(ServiceHelper helper) -- this is less good because the field can't be final.

Or by making the field public -- I assume you know the reasons this is bad.


If you're determined to have a private field that's not set by a public constructor or setter, you could use Spring's ReflectionUtils.setField() from within your test:

 @Mock
 private ServiceHelper serviceHelper;

 private TestService service;

 @Before
 public void configureService() {
     service = new TestService();
     Field field = ReflectionUtils.findField(TestService.class, "serviceHelper");
     ReflectionUtils.setField(field, service, serviceHelper);
 }

(Or, equally, use JDK's reflection classes directly, or reflection utils from elsewhere)

This is explicitly using reflection to subvert the access rules you've coded into the class. I thoroughly recommend option 2.

Upvotes: 8

Moe
Moe

Reputation: 2852

Try using constructor injection it'd be easier to mock the classes for testing... here's an example on how I would structure my classes to get you going. When you write your tests you now have to pass the Mocked object into the instance you're creating of these classes:

@Service
public class TestServiceImpl implements TestService {
  private TestDao testDao;
  private ServiceHelper serviceHelper;

  @Autowired
  public TestServiceImpl(TestDAO testDAO, ServiceHelper serviceHelper) {
    this.testDAO = testDAO;
    this.serviceHelper = serviceHelper;
  }
}

@Repository
public class ServiceHelper {
  private ResponseHeader responseHeader;

  @Autowired
  public ServiceHelper(ResponseHeader responseHeader) {
    this.responseHeader = responseHeader  
  }
}

Upvotes: 2

Plog
Plog

Reputation: 9612

I think the issue may be that you are stubbing your method to return the same object which you then assign the result of the method under test. i.e. (the results object here):

ResultsModel results = new ResultsModel();

when(sHelper.getResults(map)).thenReturn(results);
results = serviceImpl.getResults(map);

This will probably cause some sort of cyclic confusion when it tries to stub the method in Mockito, and it certainly won't make your assertation pass:

Assert.assertEquals(results.getStatus(), "Success");

Since the status on results is never set anywhere.

I think you need to make separate objects for your stubbing and your returned value from the method under test and make sure you set your stubbed one to have a status of "Success":

ResultsModel results = new ResultsModel();
results.setStatus("Success");
when(sHelper.getResults(map)).thenReturn(results);
ResultsModel returnedResults = serviceImpl.getResults(map);
Assert.assertEquals(returnedResults.getStatus(), "Success");

Upvotes: 2

Related Questions