Question

The problem

A presenter that "manages" a passive view subscribes to events that occur in that view (e.g. button click), and does not directly expose the methods that handle those events as public interface. I don't like the idea to make those methods public just for unit-testing since it smells like exposing the internal implementation details. Therefore, calling that event handling code becomes quite non-trivial.

My solution

The view mock has to "intercept" the event subscription and then the corresponding intercepted listener is used to call the event handling code. My implementation includes a utility class that implements the Answer interface from the Mockito API

   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;
          }

   }

The event subscription is intercepted in the following way

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

After creating the SUT instance...

XyzPresenter sut = new XyzPresenter(xyzView);

...I obtain the listener

OnEventListener xyzViewTouchListener = xyzViewTouchedListenerInterceptor.getArg();

In the "Act" part I call the event handling method of the listener

xyzViewTouchListener.onEvent();

The question

I'm quite new to unit testing in Java, so I'd like to know if there's any more elegant way of testing the presenter code. The current "Arrange" part is quite bloated an does not seem to excel in readability.

Edit: Adding the simplified SUT code on Jonathan's request. It illustrates that the presenter does not have any public methods (except constructor), and subscribes to the view events.

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
    }
}
Was it helpful?

Solution

Basically I also use ArgumentCaptor in this setup.

The basic layout of my presenter tests is like this :

@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
}

OTHER TIPS

Thanks to @Jonathan for suggesting ArgumentCaptor, I can use it instead of my "re-invented wheel" ArgumentRetrievingAnswer. I managed to stub void methods for event subscribing to use ArgumentCaptor, although it has some after-taste of a hack.

ArgumentCaptor<OnEventListener> xyzViewTouchedListenerCaptor = 
                ArgumentCaptor.forClass(OnEventListener.class);    
doNothing().when(xyzView).addViewTouchedListener(xyzViewTouchedListenerCaptor.capture());
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top