使用Mockito对带有被动视图的演示者进行单元测试



问题

一个"管理"被动视图的呈现者订阅该视图中发生的事件(例如按钮点击),而不直接将处理这些事件的方法作为公共接口公开。我不喜欢仅仅为了单元测试而公开这些方法的想法,因为它有暴露内部实现细节的味道。因此,调用事件处理代码变得非常重要。

我的解决方案

视图模拟必须"拦截"事件订阅,然后使用相应的拦截侦听器调用事件处理代码。我的实现包括一个实用程序类,它实现了来自Mockito API的Answer接口

   private class ArgumentRetrievingAnswer<TArg> implements Answer {           
          private TArg _arg;
          @Override
          public Object answer(InvocationOnMock invocation) {
                 Object[] args = invocation.getArguments();
                 _arg = (TArg)args[0];
                 return null;
          }
          public TArg getArg() {
                 return _arg;
          }
   }

以以下方式拦截事件订阅

      XyzView xyzView = mock(XyzView.class);
      ArgumentRetrievingAnswer<OnEventListener> xyzViewTouchedListenerInterceptor = 
                   new ArgumentRetrievingAnswer<OnEventListener>();
      doAnswer(xyzViewTouchedListenerInterceptor)
             .when(xyzView).addViewTouchedListener(any(OnEventListener.class));

创建SUT实例后…

XyzPresenter sut = new XyzPresenter(xyzView);

…我获得了监听器

OnEventListener xyzViewTouchListener = xyzViewTouchedListenerInterceptor.getArg();

在"Act"部分,我调用了侦听器的事件处理方法

xyzViewTouchListener.onEvent();

我对Java中的单元测试很陌生,所以我想知道是否有更优雅的方法来测试演示器代码。当前的"安排"部分相当臃肿,似乎在可读性方面表现不佳。

编辑:

应Jonathan的要求添加简化的SUT代码。它说明演示者没有任何公共方法(除了构造函数),并订阅视图事件。

public interface XyzView {
    void setInfoPanelCaptionText(String text);
    void addViewInitializedListener(OnEventListener listener);
    void addViewTouchedListener(OnEventListener listener);
}

public class XyzPresenter {
    private XyzView _xyzView;
    private OnEventListener _xyzViewTouchedListener = new OnEventListener() {       
        @Override
        public void onEvent() {
            handleXyzViewTouch();   
        }
    };
    public XyzPresenter(XyzView xyzView) {
        _xyzView = xyzView;
        _xyzView.addViewTouchedListener(_xyzViewTouchedListener);
    }
    private void handleXyzViewTouch() {
        // event handling code
    }
}

基本上我在这个设置中也使用ArgumentCaptor

我的演示者测试的基本布局是这样的:

@RunWith(MockitoJUnitRunner.class)
public class PresenterTest {
    private Presenter sut;
    @Mock
    private View view;
    @Captor
    private ArgumentCaptor<ViewTouchedListener> listenerCaptor;
    private ViewTouchedListener listener;
    @Before
    public void setUp() {
        sut = new Presenter(view);
        verify(view).addViewTouchedListener(listenerCaptor.capture());
        listener = listenerCaptor.getValue();
    }
    // test methods have access to both sut and its registered listener
}

感谢@Jonathan建议ArgumentCaptor,我可以用它来代替我的"重新发明的轮子"ArgumentRetrievingAnswer。我设法存根事件订阅的void方法来使用ArgumentCaptor,尽管它有一些hack的余味。

ArgumentCaptor<OnEventListener> xyzViewTouchedListenerCaptor = 
                ArgumentCaptor.forClass(OnEventListener.class);    
doNothing().when(xyzView).addViewTouchedListener(xyzViewTouchedListenerCaptor.capture());

最新更新