Can you make mockito (1.10.17) work with default methods in interfaces?
Asked Answered
A

7

28

I am a big fan of mockito, unfortunately for one of my projects which uses Java 8, it fails on me...

Scenario:

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

Unfortunately, the test fails and foo.bar() returns 0.

When I uncomment the when() line, I get a stack trace...

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

This is the latest stable version available on maven; googling around didn't tell me much about the status of mockito with regards to this new functionality in Java 8...

Can you make it work in some other way than implementing interfaces and spy() on them (this works)?

Athwartships answered 27/12, 2014 at 0:15 Comment(3)
I suspect that this is an effect of the way that Mockito handles dynamic proxy generation for mocks and will need an update to the infrastructure. Have you checked whether there's an outstanding issue against Mockito for it?Tuck
@chrylis no, not for this point in particular; there is one issue opened related to Java 8 and default methods, and the issue opener rightfully told (I am stupid not to have thought about this at first) that he had to compile mockito with Java 8 to make the test work at all. Looks gloomy :/Athwartships
Yeah. There are still a few libraries out there that maintain forks for 1.4.Tuck
T
14

With mockito 2.x, JDK 8 default methods are supported.

With mockito 1.x it's not possible,


Old answer

Unfortunately it's not yet possible (mockito 1.10.19), from the README.md on the github'page

JDK8 status

Mockito should work fine with JDK8 if you stay away from default methods (aka defender methods). Lambda usage may work just as good for Answers. We're unsure about every JDK8 features at the moment, like serializing a mock that uses a lambda. Error report and pull request are welcome though (contributing guide).

EDIT 1: defender methods and default methods are different names for the same thing.

I hope for a mockmaker replacement that will handle java 8 opcodes properly for such cases as some opcodes have a different semantic in Java 8.

EDIT 2: Updated the mockito readme, and this quote accordingly

Trusting answered 27/12, 2014 at 13:17 Comment(5)
Uh, yeah, I saw that, but I wonder whether "defender methods" actually meant default methods in interfaces; and what is a "defender method" anyway?Athwartships
Well, OK, then, I guess I'll submit a pull request for updating the README... I don't believe many people know that those two are synonymsAthwartships
I'll update it right now :) By the way, it is incomplete in some aspect but you may want to try this build : github.com/bric3/mockito/tree/bytebuddy-mockmaker (you'll have to buid it yourself)Trusting
OK, I'll try and experiment with it. Thanks again!Athwartships
From the release note of version 1.10.0 (2014-09-25 22:25 UTC): "Allow calling real implementation of jdk8 extension methods (#39)". Aren't "extension methods" the same thing? github.com/mockito/mockito/blob/master/doc/release-notes/…Belleslettres
O
14

I just tried Mockito 2.0.38-beta, and it already works in that version. But Mockito must be told explicitly to call the default implementation.

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

when(foo.bar()).thenCallRealMethod();
assertThat(foo.bar()).isEqualTo(42);
Ozzy answered 19/1, 2016 at 0:2 Comment(0)
L
7

Mockito (this works on version 3) can use default methods. Here's the example code rewritten with JUnit 5 so it should work:

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

The calls real methods answer DOES work in the latest Mockito. As a general rule, the extensions make the creation of mocks more declarative, but the mock method also provides an overload which supports the default answer as shown in the @Mock annotation above: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#mock-java.lang.Class-org.mockito.stubbing.Answer-

Lactoscope answered 20/11, 2020 at 14:23 Comment(0)
H
2

You can get around this limitation by implementing the interface (tested in 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 { }
  }
}
Hypotrachelium answered 30/12, 2016 at 12:25 Comment(1)
I used this hint successfully in combination with @Jan X Marek's hint to get Mockito 1.10.x to have default methods on a mock.Prediction
J
2

I just ran into the same issue with Mockito (org.mockito:mockito-core:1.10.19). Problem is: I'm not able to change the Mockito version (2.7.22 would work) because of dependencies to org.springframework.boot:spring-boot-starter-test:1.4.3.RELEASE which we are using (Spring, Mockito issue).

The easiest solution I found is to implement the interface with a private abstract class within my test class and mocking that one (also compare to the solution of @Mihai Bojin). Doing it like this keeps you away from the hassle to also "implement" all methods required by the interface(s).

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());
    }
}
Jourdain answered 5/5, 2017 at 12:35 Comment(0)
M
0

Another solution would be to use a Spy as described here: https://mcmap.net/q/489759/-how-to-write-junit-for-interface-default-methods

Macilroy answered 28/10, 2020 at 13:57 Comment(0)
P
0

Just a litte side note that especially functional interfaces can be easily created without mocking. If you need to call some verify methods afterwards, you may wrap the instance into a spy.

public final class MockTest
{
    @Test
    public void testDefaultMethodsWithMocks()
    {
        final Foo foo = () -> 0;

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

    @FunctionalInterface
    private interface Foo
    {
        int foo();

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

Sometimes we are so used to work with these frameworks that we easily forget about the obvious solutions.

Peregrination answered 17/5, 2022 at 15:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.