question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Allow spying on interfaces so that it is convenient to work with Java 8 default methods

See original GitHub issue

Problem

While Mockito supports and stubs by default the default methods in interfaces, the stubbing code can be a bit non intuitive, e.g.

interface DM {
    int contract();
    default int default_contract() { return contract() + 1; }
}

DM dm = mock(DM.class);
given(dm.contract()).willReturn(2);

// necessary otherwise default method is stubbed
given(dm.default_contract()).willCallRealMethod();

assertThat(dm.default_contract()).isEqualTo(3);

This behavior is unintuitive to users who expect the default methods to trigger real implementation (callRealMethod() by default). See also user report at #940.

Suggested plan

🥇 Contributions are welcome!

  • relax validation and allow interfaces to be spied. This way users can invoke spy(SomeInterface) or use @Spy with interfaces. This way, we don’t need to mark default methods with “callRealMethod”.
  • ensure test coverage for mocking
    • interfaces with and without default methods
    • concrete classes that extend interface with default methods (perhaps already covered)
  • document this use case. On main Mockito javadoc the use can search for “default” and find information about default methods behavior. Suggested by user at #940.
  • create a separate ticket for Mockito 3 (“2.* incompatible” label) to discuss whether we should change the default behavior for defender/default methods. Perhaps they should automatically call real method regardless if someone uses spy() or mock() with the interface. Also we should consider mocking/spying on concrete classes that extend from interface with default methods.

Discontinued original plan

Below idea was discontinued:

Replace DM by Map, default_contract() by getOrDefault(), contract() by get() or containsKey() and you have a problem with designs that are used in the JDK itself.

I think mockito can improve on this by configuring the mock to invoke concrete default methods rather than stubbing them. This could be done the following way (api naming in progress) :

  • mock(DM.class, USE_DEFAULT_METHODS)

    The issue with that approach is that a default answer is mutually exclusive with other answers. e.g. if one wants to use RETURNS_SMART_NULLS and default methods this cannot work with the current design.

  • mock(DM.class, withSettings().useDefaultMethods())

    This approach is interesting as it allows to configure the behaviour with possibly any answer. However this may require some changes with our internal answers, not a deal breaker though.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:27 (25 by maintainers)

github_iconTop GitHub Comments

2reactions
fluentfuturecommented, Feb 28, 2017

Did a bit of archeology. #106 has a discussion both about the API and the naming.

In that thread I argued against spy: “All the use cases I know of for partial mocking don’t need spying”.

At the time “stub” wasn’t suggested but now I think about it, it seems to make sense.

Although, changing it may mean to introduce a new @Stub annotation, and deprecate @Spy AbstractClass, if we are willing to consider the ship not sailed yet.

0reactions
mockitoguycommented, Feb 28, 2017

Interesting discussion! Thank you for all suggestions.

the tests should not fail or should not be rewritten by replacing @Mock with @Spy or setting up special mocking settings like withSettings().useDefaultMethods().

You are right. Ideally the tests only fail when a bug is introduced.

I have a feeling the discussion is getting broader and I am not sure if still discusses the issue reported 😃 My immediate reaction to new @Stub interface and potential deprecation of @Spy is -1 because I don’t see clear value. However, please formulate a separate ticket with the use case, code samples, and the team will for sure review it!

Coming back to the original issue: relaxing spy annotation for interfaces is useful for spying on interfaces with default methods. The use case is reasonable and team is +1 to the change. Do we have new data / use cases that indicate that this change inappropriate?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Static and Default Methods in Interfaces in Java - Baeldung
In this tutorial, we'll learn how to use static and default methods in interfaces, and discuss some situations where they can be useful....
Read more >
Can you make mockito (1.10.17) work with default methods in ...
The easiest solution I found is to implement the interface with a private abstract class within my test class and mocking that one...
Read more >
Java 8 Interface Changes - static method, default method
Let's look into the default interface methods and static interface methods and the reasoning of their introduction in Java 8 interface ...
Read more >
A Unit Testing Practitioner's Guide to Everyday Mockito - Toptal
Spies might be useful for testing legacy code that can't be redesigned to ... Since Java 8, interfaces may contain default methods along...
Read more >
Mockito (Mockito 4.9.0 API) - Javadoc.io
It allows access to documentation straight from the IDE even if you work ... Mockito doesn't mock final methods so the bottom line...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found