Нарушение локальной зависимости от unit test метода пустоты

Я тренируюсь с mockito, но я немного застрял в том, как тестировать метод, зависящий от вызова метода в локальном объекте. См. Следующий пример:

public class Worker {          

    public void work() {
                   Vodka vodka = new Vodka();
                   vodka.drink();
     }
}

Этот рабочий, вместо того, чтобы выполнять свою работу, любит пить. Но я хочу добавить тест, чтобы доказать, что он пьет, пока он работает. Но нет способа сделать это, потому что я должен проверить, что метод drink() вызывается при вызове метода. Я думаю, вы согласны со мной, что это невозможно проверить, поэтому мне нужно сломать зависимость, прежде чем начинать тестирование. Вот мое первое сомнение, как вы думаете, лучший способ нарушить такую ​​зависимость? Если я просто изменю область действия водки на глобальную, я думаю, это будет нехорошо (я не хочу раскрывать ее другим частям класса). Я думал о создании factory, примерно так:

public class Worker {          

    private VodkaFactory vodkaFactory = new VodkaFactory();


    public void work() {
                   Vodka vodka = vodkaFactory.getVodka();
                   vodka.drink();
     }
}

Я не уверен, правильно ли нарушил зависимость, но то, что я хочу сделать сейчас, является тестом, который вызывается методом drink(), когда выполняется работа(). Я пробовал это без везения:

@Test
    public void
    does_the_worker_drink_while_working
    () {
        VodkaFactory vodkaFactory = mock(VodkaFactory.class);
        Vodka vodka = mock(Vodka.class);
        Worker worker = new Worker();
        worker.work();
        when(vodkaFactory.getVodka()).thenReturn(vodka);
        verify(vodka,times(1)).drink();
    }

Я высмеиваю factory, и когда будет обнаружено, что новый объект Vodka создается factory. Но затем, когда я решил проверить, что этот метод вызывает 1 раз метод drink(), mockito сообщает мне:

Wanted but not invoked:
vodka.drink();
-> at testing_void_methods_from_local_objects.WorkerSpecification.does_the_worker_drink_while_working(WorkerSpecification.java:22)
Actually, there were zero interactions with this mock.

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

Я знаю, что у mockito есть метод, называемый doAnswer(), который используется для издевательства вызова метода, как вы думаете, он может быть полезен в этом случае? Как его использовать?

UPDATE:

Я следую советам, чтобы вызвать when() до work(), а также я пытаюсь разрешить factory устанавливать извне класса:

@Test
public void
does_the_worker_drink_while_working
() {
    VodkaFactory vodkaFactory = mock(VodkaFactory.class);
    Vodka vodka = mock(Vodka.class);
    Worker worker = new Worker();
    when(vodkaFactory.getVodka()).thenReturn(vodka);
    worker.work();
    verify(vodka,times(1)).drink();
}

Теперь это производственный код:

public class Worker {          


        private VodkaFactory vodkaFactory;

        public void work() {
                       Vodka vodka = vodkaFactory.getVodka();
                       vodka.drink();
         }

         public void setVodkaFactory(VodkaFactory vodkaFactory) {
               this.vodkaFactory = vodkaFactory;
         }

Исключением, которое я получаю, является следующее:

 java.lang.NullPointerException
        at testing_void_methods_called_from_local_objects.Worker.work(Worker.java:9)

Это строка, которая говорит vodka.drink()

Извините, я все еще запутался в чем проблема.

Ответ 1

Ваш рабочий создает свой класс factory здесь:

private VodkaFactory vodkaFactory = new VodkaFactory();

Макет, который вы создаете, полностью отделен от рабочего экземпляра и, следовательно, от отсутствия взаимодействия. Чтобы заставить его работать, factory должен быть добавлен работнику из "снаружи", скажем, через впрыск конструктора.

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

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

Ответ 2

Вам нужно установить vodkaFactory:

@Test
public void
does_the_worker_drink_while_working() {
    VodkaFactory vodkaFactory = mock(VodkaFactory.class);
    Vodka vodka = mock(Vodka.class);
    Worker worker = new Worker();
    when(vodkaFactory.getVodka()).thenReturn(vodka);

    //call your setter        
    worker.setVodkaFactory(vodkaFactory);

    worker.work();
    verify(vodka,times(1)).drink();
}

Ответ 3

В коде, который вы пытаетесь проверить, есть логическая ошибка. Поскольку вы создали экземпляр VodkaFactory внутри класса Worker, и, кроме того, вы сделали это поле частным.

Лучшим решением было бы передать ссылку на VodkaFactory извне класса.

public class Worker {          

    private VodkaFactory vodkaFactory;

    public void work() {
                   Vodka vodka = vodkaFactory.getVodka();
                   vodka.drink();
     }

     public void setVodkaFactory(VodkaFactory vf) {
           vodkaFactory = vf;
     }

}

Теперь, в вашем @Test, вы можете передать свой посмеянный экземпляр VodkaFactory с помощью установщика setVodkaFactory.

Ответ 4

Это скорее комментарий, чем ответ. Кроме того, чтобы сделать зависимость factory от инъекции, вы также можете тренировать свой макет when(vodkaFactory.getVodka()).thenReturn(vodka);, прежде чем взаимодействовать с ним worker.work();

Ответ 5

Ниже приведен полный JMockit unit test, который использует метод Worker#work() в изоляции от реализации его Vodka зависимость:

@Test
public void workTest(@Mocked final Vodka mockBeverage)
{
    new Worker().work();

    new Verifications() {{ mockBeverage.drink(); times = 1; }};
}