3

I'm trying to stub this getKeyFromStream method, using the 'any' matchers. I've tried being more explicit and less explicit (anyObject()), but it seems like no matter what I try, this stub will not return the fooKey in my unit test.

I'm wondering if it is because it is protected or there is something else I'm missing or doing incorrectly. I have other when/then statements throughout the tests that are working but for some reason here, it is not.

Note: The getKeyFromStream generally uses a byteArrayInputStream, but I'm trying to match it with an InputStream, I've tried both to no avail.

public class FooKeyRetriever() //Mocked this guy
{
    public FooKey getKey(String keyName) throws KeyException {

        return getKeyFromStream(getKeyStream(keyName, false), keyName);
    }

    //Stubbed this method to return a key object which has been mocked
    protected FooKey getKeyFromStream(InputStream keyStream, String keyName){
        //Some code
        return fooKey;
    }
}

Unit Test

@Mock
private FooKeyRetriever mockKeyRetriever;

@Mock
private FooKey fooKey;

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

@Test
public void testGetFooKey() throws Exception {



    when(foo.getKeyFromStream(any(InputStream.class),any(String.class))).thenReturn(fooKey);

    FooKey fooKey = mockKeyRetriever.getKey("irrelevant_key");

    assertNotNull(fooKey);
}
Chad
  • 107
  • 1
  • 1
  • 13
  • Don't believe `protected` method can be stubbed with just Mockito. What happens if you stub `getKey`? – Sotirios Delimanolis Jul 14 '15 at 17:25
  • How are you initializing mockFooKey? – Andy Turner Jul 14 '15 at 17:26
  • @AndyTurner Sorry, I just added that in there. Mock annotations. – Chad Jul 14 '15 at 17:27
  • 1
    You try and stub calls from some object `foo` but test for results of some other object `mockFooKey`; obviously this cannot work – fge Jul 14 '15 at 17:27
  • @SotiriosDelimanolis If I stub the getKey it works – Chad Jul 14 '15 at 17:28
  • 1
    Chad - that is how you initialize foo. What about mockFooKey? – Andy Turner Jul 14 '15 at 17:28
  • I've formatted your code as provided...but your test isn't in a method annotated with `@Test`. Was that an omission? – Makoto Jul 14 '15 at 17:30
  • The same way, I'll update but they are all mock annotations – Chad Jul 14 '15 at 17:31
  • Right, so where do you get mockFooKey to use the mock behaviour configured on foo? – Andy Turner Jul 14 '15 at 17:33
  • @AndyTurner I updated the code, I think I had a semantic error. I'm mocking the key to be returned and the retriever itself. Then I add behaviour to return the mocked key when the mock retriever makes a call with a specific signature. – Chad Jul 14 '15 at 17:39
  • @Makoto sorry, I fixed this to better reflect my code. – Chad Jul 14 '15 at 17:49
  • Is this really your complete test-code? I wonder if it even compiles! As far as I'm aware, `@Mock` and `@Before` annotations as well as their accompagnion fields and methods need to be declared outside of the test-method itself but inside the test class. Moreover, you should not mock the class you are testing, but all of its dependencies! Next, you return an object in your `when(...).thenReturn(...)` statement, which is initialized afterwards? Is this really your intent? – Roman Vottner Jul 14 '15 at 18:24
  • Furthermore, as @SotiriosDelimanolis has pointed out, `protected` methods need to be [mocked differently](http://stackoverflow.com/questions/8312212/mocking-protected-method) – Roman Vottner Jul 14 '15 at 18:29

1 Answers1

18

The problem with your unit-test is, that you are trying to mock a method of your actual class which you want to test but you can't actually invoke a mock method as this will return null unless you declare a mocked return value on that invoked method. Usually, you only mock external dependencies.

There are actually two ways to create test-objects: mock and spy. The primer one will create a new object based on the class you provided which has internal state null and also return null on every invoked method. That's why you need to define certain return values for method invocations. spy on the other hand creates a real object and intercepts method invocations if "mock definitions" are defined for certain methods.

Mockito and PowerMock provide two ways of defining your mocked methods:

// method 1
when(mockedObject.methodToMock(any(Param1.class), any(Param2.class),...)
    .thenReturn(answer);
when(mockedObject, method(Dependency.class, "methodToMock", Parameter1.class, Parameter2.class, ...)
    .thenReturn(answer);

or

// method 2
doReturn(answer).when(mockedObject).methodToMock(param1, param2);

The difference is, that the method 1 will execute the methods implementation while the later one won't. This is important if you deal with spy objects as you sometimes don't want to execute the real code inside the invoked method but instead just replace the code or return a predefined value!

Although Mockito and PowerMock provide a doCallRealMethod() which you can define instead of doReturn(...) or doThrow(...), this will invoke and execute the code within your real object and ignores any mocked method return statements. Though, this is not that useful in your case where you want to mock a method of your class under test.

A method implementation can be "overwritten" by

doAnswer(Answer<T>() { 
    @Override 
    public T answer(InvocationOnMock invocation) throws Throwable {
        ...
    }
)

where you simply can declare what the logic of the invoked method should be. You can utilize this to return the mock result of the protected method therefore like this:

import static org.hamcrest.core.IsSame.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;

import java.io.InputStream;

import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class FooKeyRetrieverTest {

    @Test
    public void testGetFooKey() throws Exception {
        // Arrange
        final FooKeyRetriever sut = spy(new FooKeyRetriever());
        FooKey mockedKey = mock(FooKey.class);

        doReturn(mockedKey)
            .when(sut).getKeyFromStream(any(InputStream.class), anyString());
        doAnswer(new Answer<FooKey>() {

            public FooKey answer(InvocationOnMock invocation) throws Throwable {
                return sut.getKeyFromStream(null, "");
            }
        }).when(sut).getKey(anyString());

        // Act
        FooKey ret = sut.getKey("test");

        // Assert
        assertThat(ret, sameInstance(mockedKey));
    }
}

The code above works, however note that this has the same semantic as simply declaring a return value for the getKey(...) as

doReturn(mockedKey).when(sut).getKey(anyString());

Trying to modify only getKeyFromStream(...) with something like this:

doReturn(mockedKey)
    .when(sut).getKeyFromStream(any(InputStream.class), anyString());

without modifying getKey(...) of your System-Under-Test (SUT) won't achieve anything as the real code of getKey(...) would be executed. If you however mock the sut-object, you could not invoke the method in your // Act section as this would return null. If you try

doCallRealMethod().when(sut).getKey(anyString());

on a mock object, the real method woulb be called and as mentiond beforehand, this would also invoke the real implementations of getKeyFromStream(...) and getKeyStream(...) regardless what you specified as mock-method.

As you probably can see by yourself, mocking methods of your actual class under test is not that useful and puts more burden to you than it provides any good. Therefore, it's up to you or your enterprise' policy if you want or need to test private/protected methods at all or if you stick to testing only the public API (which I would recommend). You also have the possibility to refactor your code in order to improve testability although the primary intent of refactoring should be to improve the overall design of your code.

Community
  • 1
  • 1
Roman Vottner
  • 9,720
  • 4
  • 38
  • 48