Mocking member variables of a class using Mockito

前端 未结 9 1795
醉话见心
醉话见心 2020-12-07 10:24

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 h

相关标签:
9条回答
  • 2020-12-07 10:28

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

    Whitebox.setInternalState(first, "second", sec);
    
    0 讨论(0)
  • 2020-12-07 10:29

    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).

    0 讨论(0)
  • 2020-12-07 10:29

    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.

    0 讨论(0)
  • 2020-12-07 10:31

    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 :))

    0 讨论(0)
  • 2020-12-07 10:33

    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.

    0 讨论(0)
  • 2020-12-07 10:33

    You can mock any member variable of a Mockito Mock with ReflectionTestUtils

    ReflectionTestUtils.setField(yourMock, "memberFieldName", value);
    
    0 讨论(0)
提交回复
热议问题