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)?

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
fge
  • 119,121
  • 33
  • 254
  • 329
  • 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? – chrylis -cautiouslyoptimistic- Dec 27 '14 at 00:19
  • @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 :/ – fge Dec 27 '14 at 00:30
  • Yeah. There are still a few libraries out there that maintain forks for 1.4. – chrylis -cautiouslyoptimistic- Dec 27 '14 at 05:43

7 Answers7

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

Community
  • 1
  • 1
bric3
  • 40,072
  • 9
  • 91
  • 111
  • Uh, yeah, I saw that, but I wonder whether "defender methods" actually meant default methods in interfaces; and what is a "defender method" anyway? – fge Dec 27 '14 at 13:19
  • 2
    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 synonyms – fge Dec 27 '14 at 13:27
  • I'll update it right now :) By the way, it is incomplete in some aspect but you may want to try this build : https://github.com/bric3/mockito/tree/bytebuddy-mockmaker (you'll have to buid it yourself) – bric3 Dec 27 '14 at 13:30
  • OK, I'll try and experiment with it. Thanks again! – fge Dec 27 '14 at 14:29
  • 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? https://github.com/mockito/mockito/blob/master/doc/release-notes/official.md – FBB Sep 02 '15 at 10:01
13

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);
Jan X Marek
  • 2,464
  • 2
  • 18
  • 26
6

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-

Ashley Frieze
  • 4,993
  • 2
  • 29
  • 23
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 { }
  }
}
Mihai Bojin
  • 76
  • 1
  • 5
  • 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. – Judge Mental Mar 20 '18 at 17:37
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());
    }
}
keocra
  • 613
  • 5
  • 10
1

Another solution would be to use a Spy as described here: https://stackoverflow.com/a/54915791/3636822

tomasulo
  • 1,252
  • 8
  • 9
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.

benez
  • 1,856
  • 22
  • 28