4

I'm trying to get familiar with TDD and the Presenter First Pattern. Right now I'm stuck writing a test case for my Presenter.class. My goal is to cover the whole Presenter.class including the Action Event but I have no glue how to do it with Mockito.

Presenter.class:

public class Presenter {
IModel model;
IView view;

public Presenter(final IModel model, final IView view) {
    this.model = model;
    this.view = view;

    this.model.addModelChangesListener(new AbstractAction() {
        public void actionPerformed(ActionEvent arg0) {
            view.setText(model.getText());
        }
    });
}}

IView.class:

public interface IView {
    public void setText(String text);
}

IModel.class:

public interface IModel {
    public void setText();
    public String getText();
    public void whenModelChanges();
    public void addModelChangesListener(AbstractAction action);
}

PresenterTest.class:

@RunWith(MockitoJUnitRunner.class)
public class PresenterTest {

    @Mock
    IView view;
    @Mock
    IModel model;

    @Before
    public void setup() {
        new Presenter(model, view);
    }

    @Test
    public void test1() {
    }
}

Thanks in advance!

Oliver
  • 978
  • 6
  • 17

3 Answers3

1

At first... thank you guys!

After a while i figured out this solution and stuck to it because I don't wanted to implement any interfaces at the presenter class neither I wanted to create stub classes in my tests.

IView

public interface IView {
    public void setText(String text);
}

IModel

public interface IModel {
    public String getText();
    public void addModelChangeListener(Action a);
}

Presenter

public class Presenter {

    private IModel model;
    private IView view;

    public Presenter(final IModel model, final IView view) {
        this.model = model;
        this.view = view;

        model.addModelChangeListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                view.setText(model.getText());
            }
        });
    }
}

PresenterTest

@RunWith(MockitoJUnitRunner.class)
public class PresenterTest {

    @Mock
    IView view;

    @Mock
    IModel model;

    @Test
    public void when_model_changes_presenter_should_update_view() {
        ArgumentCaptor<Action> event = ArgumentCaptor.forClass(Action.class);

        when(model.getText()).thenReturn("test-string");
        new Presenter(model, view);
        verify(model).addModelChangeListener(event.capture());
        event.getValue().actionPerformed(null);
        verify(view).setText("test-string");
    }
}
Oliver
  • 978
  • 6
  • 17
0

In this situation, the connection between the model and the presenter is loose enough (communicating through an action listener) that you're probably better off not using a mock for the model.

You could use a real model (I'd prefer that if the real model is simple enough), or as I have in the snippet below, make a stub inside your test code.

@RunWith(MockitoJUnitRunner.class)
public class PresenterTest {

    @Mock
    IView view;

    IModel model;

    @Before
    public void setup() {
        model = new StubModel();
        new Presenter(model, view);
    }

    @Test
    public void presenterUpdatesViewWhenModelChanges() {
        model.setText("Test Text");
        verify(view).setText("Test Text");
    }

    private class StubModel implements IModel {
        private String text;
        private List<ActionListener> actionListeners;

        StubModel() {
            actionListeners = new ArrayList<ActionListener>();
        }

        @Override
        public void setText(String text) {
            this.text = text;
            whenModelChanges();
        }

        @Override
        public String getText() {
            return text;
        }

        @Override
        public void whenModelChanges() {
            for (ActionListener listener: actionListeners) {
                listener.actionPerformed(null);
            }
        }

        @Override
        public void addModelChangesListener(AbstractAction action) {
            actionListeners.add(action);
        }
    }
}

It's possible you could set up this test with a mock model on which you setup stub invocations, but to do that sensibly, you might also need a mock action, which would complicate things, as the action is created by the presenter.

This seems like a lot of test code to test essentially one line of code in your presenter class, but the biggest chunk is the stub model, which might be replaced by a real model or extracted out of this test class and shared with other tests.

Don Roby
  • 40,677
  • 6
  • 91
  • 113
  • Thank you Don for your answer. How would it look like if my triplet becomes more complex and I don't want to create such big stubs? Isn't there any way to capture the Action, execute it and then verify it? – Oliver Jul 02 '12 at 13:09
  • The reason for the stub is that in your current code, the `AbstractAction` is created in your `Presenter` code. If it were instead injected or perhaps created by an injected factory, you could mock that as well as the model, and stub calls using mockito. Less explicitly coded stub and more mock stubbing ... There also is a way to capture arguments, which might allow you to use only mocks. I might try to elaborate on that when I have more time, or perhaps someone who's actually used argument capture with Mockito will chime in. – Don Roby Jul 02 '12 at 13:13
0

This is a case where a little refactoring can go a long way. Learn to "listen" to the tests and let them drive design. The Model only needs to know that an ActionListener needs to be notified, it doesn't care whether it is an AbstractAction. Use the smallest interfaces possible in your class contracts. Here are the refactorings to make a simple test (probably too simple to be worth unit testing, but you get the idea):

Presenter.class:

public class Presenter {
  public Presenter(final IModel model, final IView v) implements ActionListener {
    this.model = model;
    this.view = v;
    model.addModelChangesListener(this);
  }

  public void actionPerformed(ActionEvent arg0) {
    view.setText(model.getText());
  }
}

IModel.class:

public interface IModel {
    public void addModelChangesListener(ActionListener action);
}

PresenterTest.class:

@RunWith(MockitoJUnitRunner.class)
public class PresenterTest {
    @Mock IView view;
    @Mock IModel model;

    @Test
    public void when_model_changes_presenter_should_update_text() {
       when(model.getText()).thenReturn("Test Text");
       Presenter p = new Presenter(model, view);
       p.actionPerformed(null);
       verify(view).setText("Test Text");
    }
}
Garrett Hall
  • 29,524
  • 10
  • 61
  • 76