使用Mockito模拟类的成员变量

时间:2012-01-24 22:58:35

标签: java mocking mockito

我是开发新手,特别是单元测试。 我想我的要求很简单,但我很想知道别人的想法。

假设我有两个类似的 -

public class First {

    Second second ;

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

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

class Second {

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

假设我正在编写单元测试来测试First.doSecond()方法。但是,假设我想像这样模拟Second.doSecond()类。我正在使用Mockito来做这件事。

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

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

我看到模拟没有生效,断言失败了。 有没有办法模拟我想测试的类的成员变量。 ?

9 个答案:

答案 0 :(得分:76)

您需要提供一种访问成员变量的方法,以便您可以传入模拟(最常见的方式是setter方法或带参数的构造函数)。

如果您的代码没有提供这样做的方法,则TDD(测试驱动开发)会错误地考虑它。

答案 1 :(得分:54)

如果您无法更改代码,则无法执行此操作。但我喜欢依赖注入,而Mockito支持它:

public class First {    
    @Resource
    Second second;

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

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

你的考试:

@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());
   }
}

这非常好而且容易。

答案 2 :(得分:31)

如果仔细查看代码,您会发现测试中的second属性仍然是Second的实例,而不是模拟(您不会将模拟传递给{{代码中的1}}。

最简单的方法是在first类中为second创建一个setter,并明确地将它传递给它。

像这样:

First

另一种方法是将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()); } 实例作为Second的构造函数参数传递。

如果您无法修改代码,我认为唯一的选择是使用反射:

First

但你可能会这样做,因为很少对你无法控制的代码进行测试(尽管人们可以想象你必须测试外部库的情况,因为它的作者没有:)

答案 3 :(得分:6)

如果您无法更改成员变量,那么另一种方法是使用powerMockit并调用

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

现在问题是任何对新Second的调用都将返回相同的模拟实例。但在你的简单案例中,这将有效。

答案 4 :(得分:6)

我有同样的问题,因为没有设置私有值,因为Mockito没有调用超级构造函数。这就是我用反射来增强模拟的方法。

首先,我创建了一个TestUtils类,其中包含许多有用的工具,包括这些反射方法。每次实现反射访问都有点不稳定。我创建了这些方法来测试项目上的代码,由于某种原因,这些方法没有模拟包,我没有被邀请包含它。

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);
        }
    }

}

然后我可以使用像这样的私有变量来测试类。这对于模拟你无法控制的深层树很有用。

@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
}

我在这里修改了我的实际项目中的代码。可能存在一两个编译问题。我想你得到了一般的想法。如果您发现它有用,请随意获取代码并使用它。

答案 5 :(得分:1)

其他很多人已经建议你重新考虑你的代码,使其更具可测性 - 好的建议,通常比我建议的更简单。

如果您无法更改代码以使其更易于测试,那么PowerMock:https://code.google.com/p/powermock/

PowerMock扩展了Mockito(因此您不必学习新的模拟框架),提供额外的功能。这包括让构造函数返回模拟的能力。功能强大,但有点复杂 - 所以明智地使用它。

你使用不同的Mock跑步者。并且您需要准备要调用构造函数的类。 (注意,这是一个常见的问题 - 准备调用构造函数的类,而不是构造的类)

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

然后在你的测试设置中,你可以使用whenNew方法让构造函数返回一个模拟

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

答案 6 :(得分:1)

您可以使用ReflectionTestUtils来模拟Mockito Mock的任何成员变量

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

答案 7 :(得分:0)

是的,这可以完成,如下面的测试所示(使用我开发的JMockit模拟API编写):

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

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

然而,对于Mockito来说,无法编写这样的测试。这是由于模拟在Mockito中实现的方式,其中创建了要模拟的类的子类;只有这个“mock”子类的实例才有模拟行为,所以你需要让测试过的代码使用它们而不是任何其他实例。

答案 8 :(得分:0)

如果您希望在模仿中替代Spring中的 ReflectionTestUtils ,请使用

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