Allow spying on interfaces so that it is convenient to work with Java 8 default methods
See original GitHub issueProblem
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:
- Created 7 years ago
- Comments:27 (25 by maintainers)
Top GitHub Comments
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.Interesting discussion! Thank you for all suggestions.
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?