Java 使用Mockito模拟类的成员变量

Java 使用Mockito模拟类的成员变量,java,mocking,mockito,Java,Mocking,Mockito,我是开发新手,尤其是单元测试新手。 我想我的要求很简单,但我很想知道其他人对此的想法 假设我有两个这样的班- public class First { Second second ; public First(){ second = new Second(); } public String doSecond(){ return second.doSecond(); } } class Second { p

我是开发新手,尤其是单元测试新手。 我想我的要求很简单,但我很想知道其他人对此的想法

假设我有两个这样的班-

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());
}
我看到嘲弄没有生效,断言也失败了。
没有办法模拟我要测试的类的成员变量吗

如果仔细查看代码,您会发现测试中的
second
属性仍然是
second
的实例,而不是mock(代码中没有将mock传递给
first

最简单的方法是在
First
类中为
second
创建一个setter,并显式地将其传递给mock

像这样:

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

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

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

但是您可能可以,因为很少对不受控制的代码进行测试(尽管您可以想象这样一种场景:您必须测试外部库,因为它的作者没有:)

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


如果您的代码没有提供实现这一点的方法,那么TDD(测试驱动开发)将错误地考虑它。

如果您不能更改代码,这是不可能的。但是我喜欢依赖项注入,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());
   }
}

这非常好而且简单。

是的,这是可以做到的,如以下测试所示(使用我开发的JMockit mocking API编写):


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

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

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

现在的问题是,对new Second的任何调用都将返回相同的模拟实例。但在您的简单示例中,这将起作用。

许多其他人已经建议您重新考虑代码,使其更易于测试-这是一个很好的建议,通常比我将要建议的更简单

如果无法更改代码以使其更易于测试,请使用PowerMock:

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

您使用不同的模拟运行程序。您需要准备要调用构造函数的类。(注意,这是一个常见的问题-准备调用构造函数的类,而不是构造的类)

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

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

我也遇到过同样的问题,没有设置私有值,因为Mockito不调用超级构造函数

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

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

}

我在这里的第页修改了我实际项目中的代码。可能有一两个编译问题。我想你已经了解了大概的意思。如果你觉得代码有用,请随意获取并使用它。

如果你想要mockito中Spring的ReflectionTestUtils的替代品,请使用

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

您可以使用
ReflectionTestUtils

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

明白了。我可能会同意你的第一个建议。只是好奇,你知道有什么方法或API可以在应用程序级或包级模拟对象/方法吗?我想我说的是,在上面的例子中,当我模拟“Second”对象时,有没有一种方法可以覆盖lifecyc中使用的每个Second实例测试之乐。?@AnandHemmige实际上是第二个(构造器)更干净,因为它避免了创建不必要的“第二个”实例。您的类通过这种方式得到了很好的解耦。Mockito提供了一些很好的注释,让您将Mock注入到私有变量中。使用
@Mock
注释第二个,使用
@InjectMocks
注释第一个,并在初始值设定项中实例化第一个。Mockito将自动执行最好找到一个位置将第二个mock注入第一个实例,包括设置与类型匹配的私有字段。
@mock
在1.5版本中就已经出现了(可能更早,我不确定).1.8.3介绍了
@InjectMocks
以及
@Spy
@Captor
。谢谢。我看到了。我只是想知道,在可能有许多内部方法、类需要模拟,但不一定可以通过setXXX()设置的情况下,如何使用mock执行集成测试在此之前。使用依赖项注入框架和测试配置。绘制您试图进行的集成测试的序列图。将序列图分解为您可以实际控制的对象。这意味着如果您使用的框架类具有上面显示的依赖对象反模式,则应将对象及其分解不良的成员视为序列图中的单个单元
@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
}
Whitebox.setInternalState(first, "second", sec);
ReflectionTestUtils.setField(yourMock, "memberFieldName", value);