Можете ли вы заставить mockito (1.10.17) работать с методами по умолчанию в интерфейсах?

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

Сценарий:

public final class MockTest
{
    @Test
    public void testDefaultMethodsWithMocks()
    {
        final Foo foo = mock(Foo.class);

        //when(foo.bar()).thenCallRealMethod();

        assertThat(foo.bar()).isEqualTo(42);
    }

    @FunctionalInterface
    private interface Foo
    {
        int foo();

        default int bar()
        {
            return 42;
        }
    }
}

К сожалению, тест не проходит и foo.bar() возвращает 0.

Когда я раскомментирую строку when(), я получаю трассировку стека...

java.lang.NoSuchMethodError: java.lang.Object.bar()I
    at com.github.fge.lambdas.MockTest.testDefaultMethodsWithMocks(MockTest.java:18)

Это последняя стабильная версия, доступная на maven; поиск в Google мало что сказал мне о статусе mockito в отношении этой новой функциональности в Java 8...

Можете ли вы заставить его работать каким-то другим способом, кроме реализации интерфейсов и spy() на них (это работает)?


person fge    schedule 27.12.2014    source источник
comment
Я подозреваю, что это связано с тем, как Mockito обрабатывает динамическую генерацию прокси для макетов, и ему потребуется обновление инфраструктуры. Вы проверили, есть ли для этого нерешенная проблема с Mockito?   -  person chrylis -cautiouslyoptimistic-    schedule 27.12.2014
comment
@chrylis нет, не конкретно по этому поводу; есть одна открытая проблема, связанная с Java 8 и методами по умолчанию, и автор проблемы справедливо сказал (я глуп, что не подумал об этом сначала), что ему пришлось скомпилировать mockito с Java 8, чтобы тест работал вообще. Выглядит мрачно :/   -  person fge    schedule 27.12.2014
comment
Ага. Есть еще несколько библиотек, поддерживающих форки для 1.4.   -  person chrylis -cautiouslyoptimistic-    schedule 27.12.2014


Ответы (6)


В mockito 2.x методы JDK 8 по умолчанию поддерживаются.

С mockito 1.x это невозможно,


Старый ответ

К сожалению, это пока невозможно (mockito 1.10.19), из README.md на странице github.

Статус JDK8

Mockito должен нормально работать с JDK8, если вы держитесь подальше от методов по умолчанию (так называемых методов защиты). Использование Lambda может работать так же хорошо для ответов. На данный момент мы не уверены во всех функциях JDK8, таких как сериализация макета, использующего лямбда-выражение. Однако отчет об ошибке и запрос на извлечение приветствуются (руководство по содействию).

EDIT 1: методы защитника и методы по умолчанию — это разные названия одного и того же.

Я надеюсь на замену mockmaker, которая будет правильно обрабатывать коды операций Java 8 для таких случаев, поскольку некоторые коды операций имеют другую семантику в Java 8.

РЕДАКТИРОВАТЬ 2: Обновлены сведения о mockito и эта цитата соответственно.

person Brice    schedule 27.12.2014
comment
О, да, я это видел, но мне интересно, действительно ли методы защитника означают методы по умолчанию в интерфейсах; и что такое метод защитника? - person fge; 27.12.2014
comment
Хорошо, тогда я отправлю запрос на обновление README... Я не думаю, что многие люди знают, что эти два слова являются синонимами. - person fge; 27.12.2014
comment
Я обновлю его прямо сейчас :) Кстати, в некоторых аспектах он неполный, но вы можете попробовать этот билд: github.com/bric3/mockito/tree/bytebuddy-mockmaker (вам придется создать его самостоятельно) - person Brice; 27.12.2014
comment
ОК, попробую поэкспериментировать. Спасибо еще раз! - person fge; 27.12.2014
comment
Из примечания к выпуску версии 1.10.0 (25.09.2014, 22:25 UTC): Разрешить вызов реальной реализации методов расширения jdk8 (#39). Разве методы расширения не одно и то же? github.com/mockito/mockito/blob/master/ документ/примечания к выпуску/ - person FBB; 02.09.2015

Я только что попробовал Mockito 2.0.38-beta, и в этой версии он уже работает. Но Mockito нужно явно указать, чтобы он вызывал реализацию по умолчанию.

Foo foo = mock(Foo.class);
assertThat(foo.bar()).isEqualTo(0);

when(foo.bar()).thenCallRealMethod();
assertThat(foo.bar()).isEqualTo(42);
person Jan X Marek    schedule 19.01.2016

Mockito (это работает в версии 3) может использовать методы default. Вот пример кода, переписанного с помощью JUnit 5, чтобы он работал:

@ExtendWith(MockitoExtension.class)
class MockTest {
    @Mock(answer = Answers.CALLS_REAL_METHODS)
    private Foo foo;

    @Test
    void testDefaultMethodsWithMocks() {
        assertThat(foo.bar()).isEqualTo(42);
    }

    @FunctionalInterface
    private interface Foo {
        int foo();

        default int bar() {
            return 42;
        }
    }
}

Ответы на вызовы реальных методов работают в последней версии Mockito. Как правило, расширения делают создание макетов более декларативным, но метод mock также предоставляет перегрузку, которая поддерживает ответ по умолчанию, как показано в аннотации @Mock выше: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#mock-java.lang.Class-org.mockito.stubbing.Answer-

person Ashley Frieze    schedule 20.11.2020

Вы можете обойти это ограничение, реализовав интерфейс (протестировано в Mockito 1.10.19):

public class TestClass {
  @Mock ImplementsIntWithDefaultMethods someObject;


  @Test public void test() throws Exception {
    // calling default method on mocked subtype works
    someObject.callDefaultMethod();
  }


  /* Type that implements the interface */
  static class ImplementsIntWithDefaultMethods implements IntWithDefaultMethod { }

  /* Interface you need mocked */
  interface IntWithDefaultMethod {
    default void callDefaultMethod { }
  }
}
person Mihai Bojin    schedule 30.12.2016
comment
Я успешно использовал эту подсказку в сочетании с подсказкой @Jan X Marek, чтобы заставить Mockito 1.10.x иметь методы по умолчанию для макета. - person Judge Mental; 20.03.2018

Я только что столкнулся с той же проблемой с Mockito (org.mockito:mockito-core:1.10.19). Проблема в том, что я не могу изменить версию Mockito (2.7.22 будет работать) из-за зависимостей от org.springframework.boot:spring-boot-starter-test:1.4.3.RELEASE, который мы используем (Spring, выпуск Mockito).

Самое простое решение, которое я нашел, - это реализовать интерфейс с частным абстрактным классом в моем тестовом классе и издеваться над ним (также сравните с решением @Mihai Bojin). Делая это таким образом, вы избавляетесь от хлопот, связанных с «реализацией» всех методов, требуемых интерфейсом (ами).

MWE:

public interface InterfaceWithDefaults implements SomeOtherInterface {
    default int someConstantWithoutSense() {
        return 11;
    }
}

public class SomeTest {
    private abstract class Dummy implements InterfaceWithDefaults {}

    @Test
    public void testConstant() {
        InterfaceWithDefaults iwd = Mockito.mock(Dummy.class);

        Assert.assertEquals(11, iwd.someConstantWithoutSense());
    }
}
person keocra    schedule 05.05.2017

Другим решением было бы использовать Spy, как описано здесь: https://stackoverflow.com/a/54915791/3636822

person tomasulo    schedule 28.10.2020