Напишите тест Mockito для класса Presenter (шаблон First Presenter)

Я пытаюсь познакомиться с TDD и Presenter First Pattern. Прямо сейчас я застрял в написании тестового примера для моего Presenter.class. Моя цель - охватить весь Presenter.class, включая Action Event, но я не знаю, как это сделать с помощью 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() {
    }
}

Заранее спасибо!

3 ответа

Решение

Сначала... спасибо, ребята!

Через некоторое время я разобрался с этим решением и придерживался его, потому что я не хотел реализовывать какие-либо интерфейсы в классе презентатора, а также не хотел создавать классы-заглушки в своих тестах.

IView

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

IModel

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

Ведущий

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

В этой ситуации связь между моделью и докладчиком достаточно свободна (общение через слушателя действия), поэтому вам, вероятно, лучше не использовать макет для модели.

Вы можете использовать реальную модель (я бы предпочел, чтобы реальная модель была достаточно простой), или, как я сделал в приведенном ниже фрагменте кода, сделать заглушку внутри своего тестового кода.

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

Возможно, вы могли бы настроить этот тест с помощью фиктивной модели, в которой вы настраиваете вызовы-заглушки, но чтобы сделать это разумно, вам также может понадобиться фиктивное действие, которое усложнит ситуацию, так как действие создается докладчиком.

Это кажется большим количеством тестового кода для тестирования по существу одной строки кода в вашем классе презентатора, но самый большой кусок - это модель-заглушка, которая может быть заменена реальной моделью или извлечена из этого класса тестирования и использоваться совместно с другими тестами.

Это тот случай, когда небольшой рефакторинг может иметь большое значение. Научитесь "слушать" тесты и пусть они управляют дизайном. Model нужно только знать, что ActionListener должен быть уведомлен, ему все равно, является ли это AbstractAction, Используйте наименьший возможный интерфейс в ваших классовых контрактах. Вот рефакторинг, чтобы сделать простой тест (возможно, слишком простой, чтобы стоить модульного тестирования, но вы поняли идею):

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");
    }
}
Другие вопросы по тегам