Как издеваться над пустыми методами с помощью Mockito

939

Как смоделировать методы с возвращаемым типом void?

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

И я попытался найти пример в Интернете, но безуспешно.

Мой класс выглядит так:

public class World {

    List<Listener> listeners;

    void addListener(Listener item) {
        listeners.add(item);
    }

    void doAction(Action goal,Object obj) {
        setState("i received");
        goal.doAction(obj);
        setState("i finished");
    }

    private string state;
    //setter getter state
} 

public class WorldTest implements Listener {

    @Test public void word{
    World  w= mock(World.class);
    w.addListener(this);
    ...
    ...

    }
}

interface Listener {
    void doAction();
}

Система не запускается с макетом.

Я хочу показать вышеупомянутое состояние системы. И делать заявления в соответствии с ними.

ibrahimyilmaz
источник
6
Остерегайтесь того, что методы void для mocks ничего не делают по умолчанию!
Линия
1
@ Line, это то, что я искал. Кажется очевидным после того, как вы это скажете. Но он подчеркивает принцип насмешки: вам нужно только смоделировать методы надуманных классов для их эффектов, таких как возвращаемое значение или исключение. Спасибо!
Allenjom

Ответы:

1146

Взгляните на документы Mockito API . Поскольку связанный документ упоминает (пункт # 12) , вы можете использовать любого из doThrow(), doAnswer(), doNothing(), doReturn()семейства методов из рамок Mockito издеваться недействительными методами.

Например,

Mockito.doThrow(new Exception()).when(instance).methodName();

или если вы хотите объединить это с последующим поведением,

Mockito.doThrow(new Exception()).doNothing().when(instance).methodName();

Предполагая, что вы смотрите на издевательство над сеттером setState(String s)в классе World ниже, код использует doAnswerметод для имитации setState.

World  mockWorld = mock(World.class); 
doAnswer(new Answer<Void>() {
    public Void answer(InvocationOnMock invocation) {
      Object[] args = invocation.getArguments();
      System.out.println("called with arguments: " + Arrays.toString(args));
      return null;
    }
}).when(mockWorld).setState(anyString());
Sateesh
источник
8
@qualidafial: Да, я думаю, что параметризация для Void будет лучше, так как она лучше говорит о том, что меня не интересует тип возвращаемого значения. Я не знал об этой конструкции, спасибо за указание на это.
Сатиш
2
doThrow сейчас # 5 (для меня также использование doThrow исправило сообщение «тип void» здесь запрещен », для подписчиков ...)
rogerdpack
@qualidafial: Я думаю, что возвращаемый тип вызова Answer.answer - это не то, что возвращается в исходный метод, а то, что возвращается в вызов doAnswer, предположительно, если вы хотите сделать что-то еще с этим значением в своем тесте.
двенадцать17
1
:( при попытке Mock версии 16.0.1 RateLimiter.java в guava doNothing (). when (mockLimiterReject) .setRate (100) приводит к вызову setRate RateLimiter, что приводит к нулевому указателю, так как мьютекс по какой-то причине является нулевым после того, как mockito байт-кодирован это так, что он не издевался над моим методом setRate :( но вместо этого вызвал его :(
Дин Хиллер
2
@DeanHiller замечает, что setRate()есть final, и поэтому не может быть осмеяно. Вместо этого попробуйте create()- экземпляр, который делает то, что вам нужно. Там не должно быть необходимости издеваться RateLimiter.
dimo414
113

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

Mockito.doCallRealMethod().when(<objectInstance>).<method>();
<objectInstance>.<method>();

Или вы можете вызвать реальный метод для всех методов этого класса, выполнив это:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);
MarcioB
источник
13
Это настоящий ответ прямо здесь. Метод spy () работает нормально, но обычно зарезервирован для случаев, когда вы хотите, чтобы объект выполнял почти все нормально.
biggusjimmus
1
Что это значит? Вы на самом деле вызываете методы? Я действительно не использовал mockito раньше.
obesechicken13
Да, макет будет вызывать реальные методы. Если вы используете @Mock, то можете указать то же самое с помощью: @Mock (answer = Answerers.CALLS_REAL_METHODS), чтобы получить те же результаты.
Эль
70

В дополнение к тому, что сказал @sateesh, когда вы просто хотите смоделировать метод void, чтобы тест не вызывал его, вы можете использовать Spyэтот способ:

World world = new World();
World spy = Mockito.spy(world);
Mockito.doNothing().when(spy).methodToMock();

Если вы хотите запустить тест, убедитесь, что вы вызываете тестируемый метод для spyобъекта, а не для worldобъекта. Например:

assertEquals(0,spy.methodToTestThatShouldReturnZero());
Omri374
источник
57

Решением так называемой проблемы является использование spy Mockito.spy (...) вместо mock Mockito.mock (..) .

Шпион позволяет нам частичное издевательство. Мокито хорош в этом вопросе. Поскольку у вас есть класс, который не завершен, таким образом, вы издеваетесь над необходимым местом в этом классе.

ibrahimyilmaz
источник
3
Я наткнулся здесь, потому что у меня была похожая проблема (также, по совпадению, случилось, что я тестировал взаимодействие субъекта / наблюдателя). Я уже использую шпиона, но я хочу, чтобы метод SubjectChanged делал что-то другое. Я мог бы использовать `verify (наблюдатель) .subjectChanged (subject) просто чтобы увидеть, что метод был вызван. Но по какой-то причине я бы предпочел переопределить метод. Для этого, комбинация подхода Сатиша и вашего ответа здесь была способом пойти ...
gMale
32
Нет, это не поможет с насмешливыми методами. Хитрость заключается в том, чтобы использовать один из четырех статических методов Mockito, перечисленных в ответе Сатиша.
Дауд ибн Карим
2
@Gurnard на ваш вопрос, посмотрите на этот stackoverflow.com/questions/1087339/… .
Ибрагимильмаз
Этот доцент на самом деле работает.
Nilotpal
34

Прежде всего: вы всегда должны импортировать mockito static, таким образом код будет намного более читабельным (и интуитивно понятным):

import static org.mockito.Mockito.*;

Для частичной насмешки и при сохранении оригинальной функциональности в остальном мокито предлагает «Шпион».

Вы можете использовать его следующим образом:

private World world = spy(World.class);

Чтобы исключить выполнение метода, вы можете использовать что-то вроде этого:

doNothing().when(someObject).someMethod(anyObject());

чтобы придать некоторое поведение методу, используйте «when» с «thenReturn»:

doReturn("something").when(this.world).someMethod(anyObject());

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

fl0w
источник
4
Как статический импорт связан с тем, чтобы сделать его более читабельным?
Jsonbourne
2
буквально ничего.
специалист
2
Я думаю, что это дело вкуса, я просто хотел бы, чтобы выражение выглядело (почти) как английское предложение, а Class.methodname (). Кое-что () против methodname (). Кое-что менее плавно читается.
fl0w
1
Если вы работаете в команде, то при импорте статических файлов кому-то еще будет больно видеть, откуда исходит метод, поскольку в импорте часто используется подстановочный знак.
LowKeyEnergy
это правильно, однако для тестового кода и Mockito это должно быть очевидно, а не проблемой.
fl0w
27

Как смоделировать пустые методы с помощью mockito - есть два варианта:

  1. doAnswer - Если мы хотим, чтобы наш метод mocked void что-то делал (смоделируйте поведение, несмотря на то, что void).
  2. doThrow- Тогда есть, Mockito.doThrow()если вы хотите выбросить исключение из метода mocked void.

Ниже приведен пример того, как его использовать (не идеальный вариант использования, а просто хотел проиллюстрировать основное использование).

@Test
public void testUpdate() {

    doAnswer(new Answer<Void>() {

        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            Object[] arguments = invocation.getArguments();
            if (arguments != null && arguments.length > 1 && arguments[0] != null && arguments[1] != null) {

                Customer customer = (Customer) arguments[0];
                String email = (String) arguments[1];
                customer.setEmail(email);

            }
            return null;
        }
    }).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("old@test.com", "new@test.com");

    //some asserts
    assertThat(customer, is(notNullValue()));
    assertThat(customer.getEmail(), is(equalTo("new@test.com")));

}

@Test(expected = RuntimeException.class)
public void testUpdate_throwsException() {

    doThrow(RuntimeException.class).when(daoMock).updateEmail(any(Customer.class), any(String.class));

    // calling the method under test
    Customer customer = service.changeEmail("old@test.com", "new@test.com");

}
}

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

Дилини Раджапакша
источник
1
Отличный пример. Примечание: в Java 8 было бы немного лучше использовать лямбду вместо анонимного класса: вызов doAnswer ((Answer <Void>) -> {// CODE}). When (mockInstance) .add (метод ());»
MIWE
16

Добавление еще одного ответа в группу (не каламбур) ...

Вам нужно вызвать метод doAnswer, если вы не можете \ не хотите использовать шпионы. Тем не менее, вам не обязательно бросать свой собственный ответ . Есть несколько реализаций по умолчанию. В частности, CallsRealMethods .

На практике это выглядит примерно так:

doAnswer(new CallsRealMethods()).when(mock)
        .voidMethod(any(SomeParamClass.class));

Или:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));
javamonkey79
источник
16

В Java 8 это можно сделать немного чище, если у вас есть статический импорт для org.mockito.Mockito.doAnswer:

doAnswer(i -> {
  // Do stuff with i.getArguments() here
  return null;
}).when(*mock*).*method*(*methodArguments*);

return null; важно, и без этого компиляция не удастся с некоторыми довольно неясными ошибками, поскольку она не сможет найти подходящее переопределение для doAnswer.

Например, ExecutorServiceтот, который просто немедленно выполняет любой Runnableпередаваемый объект , execute()может быть реализован с использованием:

doAnswer(i -> {
  ((Runnable) i.getArguments()[0]).run();
  return null;
}).when(executor).execute(any());
Тим Б
источник
2
В одной строке: Mockito.doAnswer ((i) -> null) .when (instance) .method (any ());
Акшай Торв
@AkshayThorve Это не работает, когда вы действительно хотите что-то делать со мной, хотя.
Тим Б
7

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

Если вы реализуете слушателя как Mock, вы можете проверить взаимодействие.

Listener listener = mock(Listener.class);
w.addListener(listener);
world.doAction(..);
verify(listener).doAction();

Это должно убедить вас, что «Мир» поступает правильно.

Ashley
источник
0

Использование Mockito.doThrow как в:

Mockito.doThrow(new Exception()).when(instance).methodName();

Вы можете попробовать этот хороший пример:

public void testCloseStreamOnException() throws Exception {
    OutputStream outputStream = Mockito.mock(OutputStream.class);
    IFileOutputStream ifos = new IFileOutputStream(outputStream);
    Mockito.doThrow(new IOException("Dummy Exception")).when(outputStream).flush();
    try {
      ifos.close();
      fail("IOException is not thrown");
    } catch (IOException ioe) {
      assertEquals("Dummy Exception", ioe.getMessage());
    }
    Mockito.verify(outputStream).close();
  }

Источник: http://apisonar.com/java-examples/org.mockito.Mockito.doThrow.html#Example-19

APISonar
источник