使用工厂方法在Java中创建常见的Mockito stubbings

时间:2018-02-16 15:16:01

标签: java unit-testing mockito

在我正在研究的项目中,我们有一堆常用的助手。请考虑以下示例:

public class ServiceHelper {
    public HttpServletRequest() getRequest() { ... }
    public Model getModel() { ... }
    public UserCache getUserCache() { ... }
    public ComponentContainer getComponentContainer() { ... }
}

想象一下,我们拥有的每个Web服务都会在整个应用程序中使用此帮助程序。然后,为了测试这些服务,我需要模拟它。每一次。但是,如果我创建某种类型的工厂,例如:

public class ServiceHelperMockStore {

  public static ServiceHelper create() {
    return init();
  }

  public static ServiceHelper create(final Model model) {
    final ServiceHelper helper = init();
    when(helper.getModel()).thenReturn(model);
    return helper;
  }

  private static ServiceHelper init() {
    final ServiceHelper helper = mock(ServiceHelper.class);

    final HttpServletRequest request = mock(HttpServletRequest.class);
    final Model model = mock(Model.class);
    final UserCache userCache = mock(UserCache.class);
    final ComponentContainer container = mock(ComponentContainer.class);
    final BusinessRules businessRules= mock(BusinessRules.class);
    final ModelTransformer modelTransformer = mock(ModelTransformer.class);

    when(helper.getRequest()).thenReturn(request);
    when(helper.getModel()).thenReturn(model);
    when(helper.getUserCache()).thenReturn(userCache);
    when(helper.getComponentContainer()).thenReturn(container);
    when(container.getComponent(BusinessRules.class)).thenReturn(businessRules);
    when(componentContainer.getComponent(ModelTransformer.class)).thenReturn(modelTransformer);

    return helper;
  }
}

这个工厂非常适合我的目的,通常我可以完全避免在实际的测试套件中使用'mock'和'when'。相反,我可以做以下事情:

@RunWith(MockitoJUnitRunner.Silent.class)
public class ModelServiceTest {

  private final Model model = new Model();
  private final ServiceHelper serviceHelper = ServiceHelperMockStore.create(model);
  private final BusinessRules businessRules = serviceHelper.getComponentContainer().getComponent(BusinessRules.class);

  private final ModelType modelType1 = new ModelType();
  private final ModelType modelType2 = new ModelType();

  private final ModelService modelService = new ModelService(serviceHelper);

  @Before
  public void setUp() {
    modelType1.setItemId("item1");
    modelType2.setItemId("item2");
    model.setTypes(modelType1, modelType2);
    when(businessRules.get("type")).thenReturn(modelType1);
  }

  ...tests...
}

因此,我不是在ModelServiceTest中创建大量的模拟,而是可以访问预定义的模拟,例如:

BusinessRules businessRules = serviceHelper.getComponentContainer().getComponent(BusinessRules.class);

这甚至反映了我帮助者的API。另外,我可以将自己的模拟或存根传递参数提供给我的工厂方法或使用一些不同的方法。

我遇到的唯一问题是Mockito抛出的 UnurcessaryStubbingException ,因为通常我不会使用我为每个测试文件创建的所有那些截断。因此,我必须使用 MockitoJUnitRunner.Silent 运行程序来默认错误,并且根据mockito api docs不建议这样做。

所以我正在寻求建议在这种情况下必须采用何种方法。我做得对还是还有其他方法?或者,使用这种类型的工厂可能是与单元测试相关的糟糕编程风格,因为它隐藏了一些初始化并使发生的事情变得不那么明显,所以我必须在测试套件之间只做一份我的代码的简单副本?

1 个答案:

答案 0 :(得分:0)

您需要在不同位置使用相同的复杂模拟配置,这表明您的代码违反了 Demeter法则(不要与陌生人交谈)。

一个单元应该只获得它实际与之交互的依赖关系(除了从它获得另一个依赖关系之外)。

  

因此,我没有在ModelServiceTest中创建大量的模拟,而是可以访问预定义的模拟,

您的单元测试不仅验证正确的行为,还包括如何使用CUT的最小示例(正在测试的代码)。

CUT依赖关系的配置是该示例的重要部分,并且应该易于被测试的读者访问。

我强烈反对“嘲笑工厂”,尤其是他们被转移到其他班级(test文件夹中)。