Як знущатися з недійсних методів з Mockito


937

Як знущатися над методами з недійсним типом повернення?

Я реалізував схему спостерігачів, але не можу знущатися з Мокіто, тому що не знаю як.

І я спробував знайти приклад в Інтернеті, але це не вдалося.

Мій клас виглядає так:

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

Система не спрацьовує з макетом.

Я хочу показати вищезгаданий стан системи. І робити твердження відповідно до них.


6
Остерігайтеся, що недійсні методи на макетах не роблять нічого за замовчуванням!
Лінія

1
@Line, саме це я шукав. Це здається очевидним після того, як ти це скажеш. Але все ж підкреслює глузливий принцип: Вам потрібно лише знущатися над методами знущаються класів щодо їх ефектів, як повернене значення чи виняток. Дякую!
allenjom

Відповіді:


1144

Погляньте на документи 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)у класі Світ нижче, код використовується 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());

8
@qualidafial: Так, я думаю, що параметризація до Void була б кращою, оскільки це краще підкреслить, що мене не цікавить тип повернення. Я не знав про цю конструкцію, дякую, що вказав на це.
sateesh

2
doThrow зараз №5 (також для мене, використовуючи doThrow, це виправлено повідомлення "тип недійсних" тут не дозволений ", для послідовників ...)
rogerdpack

@qualidafial: Я думаю, що тип повернення виклику Answer.answer - це не те, що повертається до початкового методу, це те, що повертається до виклику doAnswer, імовірно, якщо ви хочете зробити щось інше з цим значенням у вашому тесті.
дванадцять17

1
:( в спробі Mock версії 16.0.1 RateLimiter.java в guava doNothing (). коли (mockLimiterReject) .setRate (100) призводить до виклику teh setRate RateLimiter, що призводить до nullpointer, оскільки mutex з якихось причин нульовий після того, як mockito bytecoded це так, що він не знущався над моїм методом setRate :( а натомість назвав це :(
Дін Гіллер

2
@DeanHiller зауважує, що setRate()це final, і тому його не можна глузувати. Замість цього спробуйте create()екземпляр, який виконує те, що вам потрібно. Не слід знущатися RateLimiter.
dimo414

113

Я думаю, що я знайшов більш просту відповідь на це запитання, щоб викликати реальний метод лише для одного методу (навіть якщо він має недійсне повернення), ви можете зробити це:

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

Або ви можете назвати реальний метод для всіх методів цього класу, роблячи це:

<Object> <objectInstance> = mock(<Object>.class, Mockito.CALLS_REAL_METHODS);

13
Це справжня відповідь саме тут. Метод spy () працює чудово, але, як правило, зарезервований для тих випадків, коли ви хочете, щоб об'єкт виконував більшу частину всього нормального.
biggusjimmus

1
Що це означає? Ви насправді викликаєте методи? Я раніше не дуже використовував мокіто.
obesechicken13

Так, макет буде називати реальні методи. Якщо ви використовуєте @Mock, ви можете вказати те саме: @Mock (відповідь = Відповіді.CALLS_REAL_METHODS) для отримання однакових результатів.
Але

70

Додавши до сказаного @sateesh, коли ви просто хочете знущатися з методом недійсності, щоб запобігти виклику тесту, ви можете використовувати Spyтакий спосіб:

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

Коли ви хочете запустити свій тест, переконайтесь, що ви викликаєте метод у тесті на spyоб'єкті, а не на worldоб'єкті. Наприклад:

assertEquals(0,spy.methodToTestThatShouldReturnZero());

57

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

Шпигун дозволяє нам частково глузувати. Мокіто добре в цьому питанні. Оскільки у вас є незакінчений клас, таким чином ви знущаєтесь з якогось необхідного місця в цьому класі.


3
Я натрапив сюди, тому що у мене була подібна проблема (також, випадково, трапилось тестування взаємодії суб'єкт / спостерігач). Я вже використовую шпигуна, але я хочу, щоб метод "SubjectChanged" робив щось інше. Я міг би використовувати `verify (спостерігач) .subjectChanged (тема) лише для того, щоб побачити, що метод викликався. Але я чомусь швидше перекрию метод. З цього приводу поєднання підходу Сатеші і вашої відповіді тут було способом іти ...
gMale

32
Ні, робити це насправді не допоможе знущатися з методів недійсності. Хитрість полягає у використанні одного з чотирьох статичних методів Мокіто, перелічених у відповіді сатеше.
Dawood ibn Kareem

2
@Gurnard для вашого питання подивіться на цей stackoverflow.com/questions/1087339/… .
ibrahimyilmaz

Цей дозант насправді працює.
Нілотпал

34

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

import static org.mockito.Mockito.*;

Для часткового глузування та збереження оригінальної функціональності на відпочинку мокіто пропонує "Шпигун".

Ви можете використовувати його наступним чином:

private World world = spy(World.class);

Щоб уникнути виконання методу, ви можете використовувати щось подібне:

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

щоб надати деяку власну поведінку методу використання "коли" з "thenReturn":

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

Щоб отримати більше прикладів, будь ласка, знайдіть чудові зразки макетів у документі.


4
Що має статичний імпорт, щоб зробити його більш читабельним?
jsonbourne

2
буквально нічого.
спеціаліз

2
Я думаю, що це питання смаку, я просто люблю, щоб вислів виглядав (майже) як англійське речення, а Class.methodname (). Щось () проти методу () .щось читається менш легко.
fl0w

1
Якщо ви працюєте над командою, імпорт статики викликає біль у дупі, щоб хтось інший бачив, звідки цей метод, оскільки часто в імпорті є підстановка.
LowKeyEnergy

це правильно, проте для тестового коду та Mockito це повинно бути очевидним, а не проблемою.
fl0w

26

Як знущатися з недійсних методів з mockito - є два варіанти:

  1. doAnswer - Якщо ми хочемо, щоб наш глузливий недійсний метод щось зробив (знущається над поведінкою, незважаючи на недійсність)
  2. doThrow- Тоді є, Mockito.doThrow()якщо ви хочете викинути виняток із методу насмішкуватості 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 ((відповідь <Void>) -> {// CODE}). Коли (mockInstance) .add (метод ()); '
ми

16

Додавання ще однієї відповіді до групи (каламбур не призначений) ...

Вам потрібно викликати метод doAnswer, якщо ви не можете \ не хочете використовувати шпигуна. Однак, вам не обов’язково прокручувати власний відповідь . Існує кілька реалізацій за замовчуванням. Зокрема, CallsRealMethods .

На практиці це виглядає приблизно так:

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

Або:

doAnswer(Answers.CALLS_REAL_METHODS.get()).when(mock)
        .voidMethod(any(SomeParamClass.class));

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 (будь-який ());
Акшай Торве

@AkshayThorve Це не працює, коли ви насправді хочете робити речі з i, хоча.
Тім Б

7

Я думаю, що ваші проблеми пов'язані з вашою структурою тесту. Мені складно поєднувати глузування з традиційним методом реалізації інтерфейсів у тестовому класі (як ви це робили тут).

Якщо ви реалізуєте слухача як Mock, ви можете перевірити взаємодію.

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

Це повинно вас переконати, що «Світ» робить правильно.


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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.