Anand Hemmige
Anand Hemmige

Reputation: 3683

Mocking member variables of a class using Mockito

I am a newbie to development and to unit tests in particular . I guess my requirement is pretty simple, but I am keen to know others thoughts on this.

Suppose I have two classes like so -

public class First {

    Second second ;

    public First(){
        second = new Second();
    }

    public String doSecond(){
        return second.doSecond();
    }
}

class Second {

    public String doSecond(){
        return "Do Something";
    }
}

Let's say I am writing unit test to test First.doSecond() method. However, suppose, i want to Mock Second.doSecond() class like so. I am using Mockito to do this.

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

I am seeing that the mocking does not take effect and the assertion fails. Is there no way to mock the member variables of a class that I want to test . ?

Upvotes: 168

Views: 417137

Answers (9)

Gayan Weerakutti
Gayan Weerakutti

Reputation: 13715

You can mock member variables of a Mockito Mock with ReflectionTestUtils

ReflectionTestUtils.setField(yourMock, "memberFieldName", value);

Upvotes: 16

soulcheck
soulcheck

Reputation: 36767

If you look closely at your code you'll see that the second property in your test is still an instance of Second, not a mock (you don't pass the mock to first in your code).

The simplest way would be to create a setter for second in First class and pass it the mock explicitly.

Like this:

public class First {

    Second second ;

    public First(){
        second = new Second();
    }

    public String doSecond(){
        return second.doSecond();
    }

    public void setSecond(Second second) {
        this.second = second;
    }


}

class Second {

    public String doSecond(){
        return "Do Something";
    }
}

....

public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");


First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}

Another would be to pass a Second instance as First's constructor parameter.

If you can't modify the code, I think the only option would be to use reflection:

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");


    First first = new First();
    Field privateField = PrivateObject.class.
        getDeclaredField("second");

    privateField.setAccessible(true);

    privateField.set(first, sec);

    assertEquals("Stubbed Second", first.doSecond());
}

But you probably can, as it's rare to do tests on code you don't control (although one can imagine a scenario where you have to test an external library cause it's author didn't :))

Upvotes: 39

Szymon Zwoliński
Szymon Zwoliński

Reputation: 39

If you want an alternative to ReflectionTestUtils from Spring in mockito, use

Whitebox.setInternalState(first, "second", sec);

Upvotes: 3

Janning Vygen
Janning Vygen

Reputation: 9192

This is not possible if you can't change your code. But I like dependency injection and Mockito supports it:

public class First {    
    @Resource
    Second second;

    public First() {
        second = new Second();
    }

    public String doSecond() {
        return second.doSecond();
    }
}

Your test:

@RunWith(MockitoJUnitRunner.class)
public class YourTest {
   @Mock
   Second second;

   @InjectMocks
   First first = new First();

   public void testFirst(){
      when(second.doSecond()).thenReturn("Stubbed Second");
      assertEquals("Stubbed Second", first.doSecond());
   }
}

This is very nice and easy.

Upvotes: 75

user650228
user650228

Reputation:

You need to provide a way of accessing the member variables so you can pass in a mock (the most common ways would be a setter method or a constructor which takes a parameter).

If your code doesn't provide a way of doing this, it's incorrectly factored for TDD (Test Driven Development).

Upvotes: 109

dave
dave

Reputation: 139

I had the same issue where a private value was not set because Mockito does not call super constructors. Here is how I augment mocking with reflection.

First, I created a TestUtils class that contains many helpful utils including these reflection methods. Reflection access is a bit wonky to implement each time. I created these methods to test code on projects that, for one reason or another, had no mocking package and I was not invited to include it.

public class TestUtils {
    // get a static class value
    public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(classToReflect, fieldNameValueToFetch);
            reflectField.setAccessible(true);
            Object reflectValue = reflectField.get(classToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // get an instance value
    public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
            Object reflectValue = reflectField.get(objToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // find a field in the class tree
    public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField = null;
            Class<?> classForReflect = classToReflect;
            do {
                try {
                    reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
                } catch (NoSuchFieldException e) {
                    classForReflect = classForReflect.getSuperclass();
                }
            } while (reflectField==null || classForReflect==null);
            reflectField.setAccessible(true);
            return reflectField;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
        }
        return null;
    }
    // set a value with no setter
    public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameToSet);
            reflectField.set(objToReflect, valueToSet);
        } catch (Exception e) {
            fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
        }
    }

}

Then I can test the class with a private variable like this. This is useful for mocking deep in class trees that you have no control as well.

@Test
public void testWithRectiveMock() throws Exception {
    // mock the base class using Mockito
    ClassToMock mock = Mockito.mock(ClassToMock.class);
    TestUtils.refectSetValue(mock, "privateVariable", "newValue");
    // and this does not prevent normal mocking
    Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
    // ... then do your asserts
}

I modified my code from my actual project here, in page. There could be a compile issue or two. I think you get the general idea. Feel free to grab the code and use it if you find it useful.

Upvotes: 7

Rog&#233;rio
Rog&#233;rio

Reputation: 16380

Yes, this can be done, as the following test shows (written with the JMockit mocking API, which I develop):

@Test
public void testFirst(@Mocked final Second sec) {
    new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

With Mockito, however, such a test cannot be written. This is due to the way mocking is implemented in Mockito, where a subclass of the class to be mocked is created; only instances of this "mock" subclass can have mocked behavior, so you need to have the tested code use them instead of any other instance.

Upvotes: -2

jwepurchase
jwepurchase

Reputation: 743

Lots of others have already advised you to rethink your code to make it more testable - good advice and usually simpler than what I'm about to suggest.

If you can't change the code to make it more testable, PowerMock: https://code.google.com/p/powermock/

PowerMock extends Mockito (so you don't have to learn a new mock framework), providing additional functionality. This includes the ability to have a constructor return a mock. Powerful, but a little complicated - so use it judiciously.

You use a different Mock runner. And you need to prepare the class that is going to invoke the constructor. (Note that this is a common gotcha - prepare the class that calls the constructor, not the constructed class)

@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})

Then in your test set-up, you can use the whenNew method to have the constructor return a mock

whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));

Upvotes: 0

user1509463
user1509463

Reputation: 121

If you can't change the member variable, then the other way around this is to use powerMockit and call

Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);

Now the problem is that ANY call to new Second will return the same mocked instance. But in your simple case this will work.

Upvotes: 7

Related Questions