Search code examples
javaunit-testingjunitejbmockito

Mockito stub method invoked using 'this' keyword


I have to test method of some SLSB which invokes another method on current object(using this keyword) and I need to stub it somehow.

Consider following simplified code:

@Local
public interface SomeService{
    public int someMethod();
    public int anotherMethod();
}

@Stateless()
public class SomeServiceImpl{

    @EJB
    private SomeDAO sDAO;

    public SomeServiceImpl(){}

    public SomeServiceImpl(SomeDAO sDAO){
         this.sDAO = sDAO;
    }

    @Override
    public int someMethod(){
        int dbValue = sDAO.getSomeDBValue(); // 1st stub required here
        return dbValue + this.anotherMethod(); // 2nd stub required here
    }

    @Override
    public int anotherMethod(){
         return 5;
    }
}

To stub getSomeDBValue() method I can inject mock to this class with @Mock and @InjectMocks annotations but i can't figure out how to correctly stub anotherMethod(). To stub it I need to do it on mock object for sure, so I tried to pass reference to current object as parameter and in test just pass mocked object. For example if my method would look like this(without need to stub DAO method)..

@Override
public int someMethod(SomeServiceImpl sS){ 
    return sS.anotherMethod(); 
}

My test with manually created mocks would look like this:

@Test
public void someMethodTest() throws Exception {
    SomeServiceImpl sS = mock(SomeServiceImpl.class);
    when(sS.someMethod(any(SomeServiceImpl.class))).thenCallRealMethod();
    when(sS.anotherMethod()).thenReturn(5);
    assertEquals(5, sS.someMethod(sS));
}

Method is invoked on mock object, reference to object itself is passed as parameter and anotherMethod is stubed. It worked but it seems very ugly solution and what if need to inject mock of my DAO using annotations like this:

@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest{

    @Mock
    SomeDAO sDAO;

    //@Mock //I can't use those 2 annotations at once
    @InjectMocks
    SomeServiceImpl sS; 

    @Test
    public void someMethodTest() throws Exception {
        //...   
    }
}

As I understand @InjectMocks annotation is used to indicate class where mocks annotated with @Mock should be injected, but for my ugly solution I need SomeServiceImpl to be mock as well.

Is my solution even close to correct? How I'm suppose to stub anotherMethod() to correctly test someMethod()? Is it a good idea to pass mocked instance of class, which method i test in method argument? If yes, how should I deal with creating mocks with annotations?


Solution

  • You should not mock one method while testing another method on the same class. You could theoretically do that (using a Mokito spy for example).

    In that sense, you are approaching this on a wrong level: you actually should not care which other methods your method under test calls within your class under test. You see, you want to test that someMethod() does fulfills its contract. If that requires a call to anotherMethod() in your production environment ... how valuable is your unit test, when it mocks anotherMethod() then?!

    Another idea: you separate concerns, and move anotherMethod() part into its own class X. And then your class under test could hold an instance of X; and that instance could then be mocked.