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

我看到嘲笑没有起作用,断言也失败了。 有没有办法模拟我要测试的类的成员变量?

347474 次浏览

如果仔细查看代码,您会发现测试中的 second属性仍然是 Second的一个实例,而不是模拟(您不会在代码中将模拟传递给 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());
}

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

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

这很简单。

是的,这是可以做到的,如下面的测试所示(使用我开发的 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 被实现的方式,在那里被模仿类的一个子类被创建,只有这个“模仿”子类的实例可以有模仿的行为,所以你需要让测试代码使用它们而不是任何其他实例。

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

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

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

许多其他人已经建议您重新考虑代码,使其更具可测试性——这是个好建议,而且通常比我将要提出的建议要简单。

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

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

您可以使用不同的模拟运行程序。并且您需要准备要调用构造函数的类。(注意,这是一个常见的陷阱——准备调用构造函数的类,而不是构造的类)

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

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

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

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

首先,我创建了一个 TestUtils 类,其中包含许多有用的工具,包括这些反射方法。每次实现反射访问都有点不稳定。我创建了这些方法来测试项目中的代码,由于这样或那样的原因,这些项目中没有 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
}

我从我的实际项目中修改了我的代码。可能有一两个编译问题。我想你大概明白了。如果您觉得这些代码有用,请随意获取并使用它们。

如果你想要一个替代的 RefectionTestUtils 反射测试工具从春天在模仿,使用

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

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

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