使用Mockito嘲笑类的成员variables

我是一个开发新手,特别是unit testing。 我想我的要求很简单,但我很想知道其他人的想法。

假设我有两个类,

public class First { Second second ; public First(){ second = new Second(); } public String doSecond(){ return second.doSecond(); } } class Second { public String doSecond(){ return "Do Something"; } } 

假设我正在编写unit testing来testingFirst.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()); } 

我看到嘲笑不生效,断言失败。 有没有办法模拟我想testing的类的成员variables。 ?

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

如果你的代码没有提供这样做的方法,那么TDD就是错误的因素。

这是不可能的,如果你不能改变你的代码。 但是我喜欢dependency injection,Mockito支持它:

 public class First { @Resource Second second ; public First(){ second = new Second(); } public String doSecond(){ return second.doSecond(); } } 

你的testing:

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

这很好,很容易。

如果仔细观察代码,您会发现testing中的second属性仍然是second属性,而不是模拟(您不会first在代码中传递模拟)。

最简单的方法是在First class中创build一个setter,并明确地传递它。

喜欢这个:

 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的构造parameter passing。

如果你不能修改代码,我认为唯一的select是使用reflection:

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

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

如果你不能改变成员variables,那么另一种方式是使用powerMockit和调用

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

现在问题是,任何调用新的第二个将返回相同的模拟实例。 但在你的简单情况下,这将工作。

我有同样的问题,私人价值没有设置,因为Mockito没有调用超级构造函数。 这是我如何用反思来增加嘲笑。

首先,我创build了一个TestUtils类,其中包含许多有用的utils,包括这些reflection方法。 每次实现reflection访问都有点难以实现。 我创build了这些方法来testing项目的代码,由于某种原因,没有模拟包,我没有被邀请包括它。

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

然后我可以用这样的私有variables来testing这个类。 这对嘲笑你无法控制的深层次的树木是有用的。

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

我在这里修改了我的实际项目的代码。 可能有一两个编译问题。 我认为你有一个总的想法。 随意获取代码,并使用它,如果你觉得有用。

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

如果您无法更改代码以使其更具可testing性,PowerMock: https : //code.google.com/p/powermock/

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

你使用一个不同的模拟亚军。 而且你需要准备要调用构造函数的类。 (请注意,这是一个常见的问题 – 准备调用构造函数的类,而不是构造类)

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

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

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

是的,可以这样做,如下面的testing所示(使用我开发的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,然而,这样的testing不能写。 这是由于在Mockito中实现模拟的方式,在Mockito中将创build要被模拟的类的子类。 只有这个“模拟”子类的实例可以有嘲弄的行为,所以你需要让testing代码使用它们而不是其他任何实例。