0

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
    }
}
Ivan Gerken
  • 914
  • 1
  • 9
  • 21
  • 1
    Thanks for adding the code, based on the update I've removed my answer. I tend only use `ArgumentCaptor` for verification, if at all, but perhaps someone else will be able to help. Good luck! – Jonathan May 17 '13 at 12:05

2 Answers2

1

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
}
bowmore
  • 10,842
  • 1
  • 35
  • 43
  • bowmore, thanks for your comment. I tested your suggestion with a minor mutation and it works properly. However, I've got some concerns about its design. 1. I think unit tests should be designed to run simultaneously (depends on runner). Therefore, making SUT and its dependencies class variables looks to me like a potential source of race condition issues. – Ivan Gerken May 20 '13 at 09:02
  • 2. In my opinion, using verify for setting up a mocked method behavior harms readability, since I don't actually need to verify that addViewTouchedListener was called. My variant smells like a hack as well but I think it expresses that I actually stub a view method a little bit better. – Ivan Gerken May 20 '13 at 09:03
  • 1
    Actually I do verify that the presenter registers a listener. If for some reason the presenter doesn't, my test will fail at that verification. If you use stubbing it will be a little harder to track down the cause of tests failing. Secondly Mockito discourages the use of ArgumentCaptors in stubbing. – bowmore May 20 '13 at 18:54
  • I've not come across runners that have a problem with a test class having class variables. I find this style reduces the boiler plate code a lot. But it's easy enough to make this thread safe, since individual tests shouldn't share state. – bowmore May 20 '13 at 19:08
0

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());
Ivan Gerken
  • 914
  • 1
  • 9
  • 21