0

问题

“管理”被动视图的演示者订阅该视图中发生的事件(例如按钮单击),并且不直接将处理这些事件的方法公开为公共接口。我不喜欢将这些方法公开仅用于单元测试的想法,因为它闻起来像是暴露了内部实现细节。因此,调用该事件处理代码变得非常重要。

我的解决方案

视图模拟必须“拦截”事件订阅,然后使用相应的拦截侦听器来调用事件处理代码。我的实现包括一个实用程序类,它实现了 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 中的单元测试很陌生,所以我想知道是否有更优雅的方式来测试演示者代码。当前的“排列”部分相当臃肿,似乎在可读性方面并不出色。

编辑: 应乔纳森的要求添加简化的 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
    }
}
4

2 回答 2

1

基本上我也在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
}
于 2013-05-18T09:10:37.713 回答
0

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

ArgumentCaptor<OnEventListener> xyzViewTouchedListenerCaptor = 
                ArgumentCaptor.forClass(OnEventListener.class);    
doNothing().when(xyzView).addViewTouchedListener(xyzViewTouchedListenerCaptor.capture());
于 2013-05-17T11:28:30.277 回答