使用Mockito模拟类的成员变量

Ana*_*ige 123 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

假设我正在为测试First.doSecond()方法编写单元测试.但是,假设,我想像Mock这样的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());
}
Run Code Online (Sandbox Code Playgroud)

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

kit*_*yst 77

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

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

  • 亲爱的@kittylyst,是的,从TDD的角度来看,或者从任何理性的观点来看,这可能是错误的.但有时开发人员在没有任何意义的地方工作,唯一的目标只是完成你分配的故事并离开.是的,这是错误的,没有任何意义,没有资格的人会接受关键的决定和所有这些事情.所以,在一天结束时,反模式赢了很多. (6认同)
  • 我对此很好奇,如果一个类成员没有理由从外部设置,为什么我们应该创建一个setter只是为了测试它?想象一下这里的“第二”类实际上是一个文件系统管理器或工具,在构建要测试的对象期间初始化。我有充分的理由想要模拟这个文件系统管理器,以便测试 First 类,并且零理由使其可访问。我可以在 Python 中做到这一点,那么为什么不使用 Mockito 呢? (4认同)
  • 谢谢.我看到了.我只是想知道,我怎么可以使用mock执行集成测试,其中可能有许多内部方法,可能需要模拟的类,但不一定可以通过setXXX()预先设置. (3认同)
  • 使用依赖注入框架和测试配置.绘制您正在尝试进行的集成测试的序列图.将序列图分解为您可以实际控制的对象.这意味着如果您正在使用一个框架类,该框架类具有您在上面显示的依赖对象反模式,那么您应该将该对象及其严重因素成员视为序列图中的单个单元.准备好调整您控制的任何代码的因子分解,使其更易于测试. (2认同)

Jan*_*ing 56

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

public class First {    
    @Resource
    Second second;

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

    public String doSecond() {
        return second.doSecond();
    }
}
Run Code Online (Sandbox Code Playgroud)

你的考试:

@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());
   }
}
Run Code Online (Sandbox Code Playgroud)

这非常好,很容易.

  • 什么是**@资源**? (7认同)
  • @IgorGanapolsky @ Resource是Java Spring框架创建/使用的注释。它是一种向Spring指示这是Spring管理的bean /对象的方法。/sf/ask/286545311/ https://www.baeldung.com/spring-annotations-resource-inject-autowire这不是模仿对象,而是因为它用于非测试类,必须在测试中进行模拟。 (3认同)
  • 我认为这是一个比其他更好的答案,因为 InjectMocks。 (2认同)

sou*_*eck 33

如果你仔细查看你的代码,你会发现second你的测试中的属性仍然是一个实例Second,而不是一个模拟(你没有first在你的代码中传递模拟).

最简单的方法是secondFirst类中创建一个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());
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是将Second实例作为First构造函数参数传递.

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

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());
}
Run Code Online (Sandbox Code Playgroud)

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

  • Mockito提供了一些很好的注释,让你将你的模拟注入私有变量.使用`@Mock`注释第二个并使用`@InjectMocks`注释First并在初始化程序中实例化First.Mockito会自动最好找到一个将第二个模拟注入First实例的地方,包括设置匹配类型的私有字段. (10认同)

use*_*463 7

如果你不能改变成员变量,那么另一种方法是使用powerMockit并调用

Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);
Run Code Online (Sandbox Code Playgroud)

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


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

}
Run Code Online (Sandbox Code Playgroud)

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

@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
}
Run Code Online (Sandbox Code Playgroud)

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


Gay*_*tti 5

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

ReflectionTestUtils.setField(yourMock, "memberFieldName", value);
Run Code Online (Sandbox Code Playgroud)