问题
一个"管理"被动视图的呈现者订阅该视图中发生的事件(例如按钮点击),而不直接将处理这些事件的方法作为公共接口公开。我不喜欢仅仅为了单元测试而公开这些方法的想法,因为它有暴露内部实现细节的味道。因此,调用事件处理代码变得非常重要。
我的解决方案视图模拟必须"拦截"事件订阅,然后使用相应的拦截侦听器调用事件处理代码。我的实现包括一个实用程序类,它实现了来自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());