使用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()方法。然而,假设我想这样MockSecond.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());
}

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

您需要提供一种访问成员变量的方法,以便传入mock(最常见的方法是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());
   }
}

这很简单。

如果仔细查看代码,您会发现测试中的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());
}

另一种方法是传递一个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());
}

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

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

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

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

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

现在的问题是,对new Second的任何调用都会返回相同的模拟实例。但在你的简单情况下,这是可行的。

我遇到了同样的问题,因为Mockito不调用超级构造函数,所以没有设置私有值。以下是我如何用反思来增加嘲讽。

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

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
}

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

如果您想要mockito中Spring的ReflectionTestUtils的替代方案,请使用

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

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

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

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

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

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

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

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

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

@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中实现mocking的方式,其中创建了要mocking的类的子类;只有这个"mock"子类的实例才能具有mock行为,所以您需要让测试的代码使用它们,而不是任何其他实例。

相关内容

  • 没有找到相关文章

最新更新