验证模拟对象中的对象被传递到 Mockito 中的参数

Verifying objects within a mocked object are passed into argument in Mockito

问题上下文

我有一个包含两个包含列表的对象的包装器 class。 (即 Class1 和 Class2 都有一个 Widget List。)

public class WrapperClass {
    Class1 class1;
    Class2 class2;
}

我有一个处理包装器的实用程序 class class

public class WrapperUtils {

    public void processClasses() {
        WrapperClass wrapperClass = getWrapperClass();
        doSomething(class1.getWidgetList());
        doSomething(class2.getWidgetList());
    }

    private WrapperClass getWrapperClass() {
        return wrapperClassFromOnlineService;
    }

    public void doSomething(List<Widget> widgetList) {}
}

目标

我想使用 Mockito 验证 doSomething 方法是否通过 class1 的小部件列表被调用。

我的尝试

@Test
public void main(String[] args) {
    WrapperClass wrapperClass = new WrapperClass();
    wrapperClass.class1 = new Class1();
    wrapperClass.class2 = new Class2();

    WrapperUtils utils = new WrapperUtils();
    Mockito.when(utils.getWrapperClass()).thenReturn(wrapperClass);

    Mockito.verify(utils, times(1)).doSomething(wrapperClass.class1.getWidgetList());
    Mockito.verify(utils, times(1)).doSomething(wrapperClass.class2.getWidgetList());
}

以上代码的结果:doSomething 注册为对两个验证语句调用两次。我的猜测是小部件列表被视为相同?

此代码存在设计问题,因此难以使用模拟进行测试。

  1. 测试没有清楚地暴露正在测试的对象和交互。
  2. 这个测试是在测试实现而不是行为,那应该是相反的。
  3. 由于问题 #1,mockito 被误用

那是什么意思

  1. 测试应显示正在测试的 case/scenario、夹具的清晰分隔、正在测试的调用以及 assertion/verification.
  2. 测试应该测试行为,即测试对象和协作者之间的交互,而不是内部的(因为它可能会在不破坏测试的情况下发生变化)。该测试还可以测试给定输入的预期输出。
  3. 如果 #1 和 #2 得到解决,那么很明显必须模拟哪种类型,并遵循教程。

这是我如何编写测试的想法,这段代码主要关注交互,但可以将断言集中在 Class 的状态上(不要在此嘲笑它们案例!!!) :

@RunWith(MockitoJUnitRunner.class)
public class WrapperUtilsTest {
    @Mock Class class1;
    @Mock Class class2;

    @Test public void ensure_that____whatever() {
        // given
        WrapperUtils tested_utils = new WrapperUtils(new WrapperClass(class1, class2));

        // when
        tested_utils.processClass();

        // then
        verify(class1).someInteraction();
        verify(class2).someInteraction();
    }
}

实现可能看起来像

public class WrapperUtils {
    private WrapperClass wrapperClass;
    public WrapperUtils(WrapperClass wrapperClass) {
        this.wrapperClass = wrapperClass;
    }

    public void processClasses() {
        doSomething(wrapperClass.class1);
        doSomething(wrapperClass.class2);
    }

    public void doSomething(Class clazz) {
        clazz.someInteraction();
    }
}

请注意,wrapperClass 通过构造函数注入 WrapperUtils 中,这有效,但您也可以传递 Supplier(在番石榴或 JDK8 中可用),该供应商可以从任何地方获取数据,例如网络服务。或者它可以是你的类型。在测试中,供应商将是一个模拟。

@RunWith(MockitoJUnitRunner.class)
public class WrapperUtilsTest {
    @Mock Class class1;
    @Mock Class class2;
    @Mock Supplier<WrapperClass> wrapped_class_supplier;

    @Test public void ensure_that____whatever() {
        // given
        BDDMockito.given(wrapped_class_supplier.get()).willReturn(new WrapperClass(class1, class2));
        WrapperUtils tested_utils = new WrapperUtils(wrapped_class_supplier);

        // when
        tested_utils.processClass();

        // then
        verify(class1).someInteraction();
        verify(class2).someInteraction();
    }
}

我强烈建议您遵循 测试 驱动 开发 方法,它确实有助于编写好的软件。还有这本书 Growing Object Oriented Software Guided by Tests 这本书很好读,这本书可能看起来很旧,但它仍然描述了最佳实践。