Join GitHub today
Hi, Here's the implementation of the partial mock support I was talking about:
The second overload is the one solution to the arbitrary constructor parameter issue:
I didn't name it spy() for the following reasons:
Annotation support isn't added yet.
Another use case I considered but would like to get some feedback before adding to the javadoc.
As the FAQ puts it:
But it leaves one question open: should one ever use the "good old hand-crafted stubs" at all? If yes, under what scenario?
A main reason that people use the
But with partial mock, the above is no longer true. It's easy enough to hand-roll an abstract stub class, implementing only the interesting methods, and then partialMock() it.
Between the two different ways of stubbing (plain old
Thanks so much for taking the time to implement this. Here's some feedback about your design decisions:
Now let's look into the code :)
Oh right, I reviewed the codes :)
One thing that stands out is that partial mock seems to be driving the API and the implementation towards a new kind of mock. Ideally, we would like to avoid it. In Mockito, we're trying to generalize more and avoid creation of new kinds of mocks.
To accept the PR, we would like to simplify the API and implementation. API:
This can be implemented incrementally. For example:
This is great stuff. Thanks for this PR. Sorry I cannot merge it in this form just yet. Perhaps I'll add some code to get this started.
Thanks for spending the time reviewing this code!
I was thinking that I cannot make spy work while also keeping final methods in fakes work.
But reading your reply a few times convinced me that I misunderstood how spies work. And it's actually pretty trivial to implement.
So, in short. I was wrong.
What do you think of the new revision?
Again. I don't feel we need to support arbitrary constructor args. It's not statically type safe or refactoring friendly; there are ambiguity issues w.r.t constructor overloads . And lastly while 0-constructor-arg abstract classes without state can be fine (like, AbstractList), abstract classes with state feels more often a design smell.
Hmm. Kinda feel that the API of
Also, unlike other mock settings that apply to every method that accepts it, outerInstance() is only meaningful to
on my radar, I'll reply tomorrow ;)
On Sat, Oct 18, 2014 at 1:31 AM, fluentfuture firstname.lastname@example.org
I agree with everything you wrote. Thing is, we cannot really objectively say that stateful abstract classes are ok only when have 0-arg constructor. Consequently, we cannot restrict the API based on this assumption. Users will come up with use case for argumented abstract classes and it will be hard to defend it. Also, Mockito needs to balance out the 'toolkit' approach (unopinionated) with 'framework' approach (opinionated). To describe this further: from the standpoint of API design we can declare that in our opinion mocking abstract classes is a design smell and we don't want to support it officially (we could however, open some extension points for users to implement it 'unofficially'). But we cannot really say that some abstract classes are ok to mock and some not. At least, this is my view on the subject and I respect if you have a different POV.
IMHO, We don't have to provide constructor args in the first iteration but we should plan for it API-wise so that adding this feature later does not incur any deprecation or API awkwardness. We should also avoid documenting that we are not planning to do the constructor args because we believe it is a smell. However, we can document that typically mocking abstract classes hints at design smell, etc.
spy() method and the withSettings() method are not "alternative" implementations :) All spy() methods need to delegate to a mock() method that has specific, spy-related withSettings(). Other words: every possible kind of mock can be created using this entry point of the API: http://mockito.github.io/mockito/docs/current/org/mockito/Mockito.html#mock(java.lang.Class,%20org.mockito.MockSettings) Think about the spy() method (and overloaded mock() methods) as a convenience methods / syntax sugar over the main mock creation entry point.
I'd rather avoid overloaded spy() method that uses outer instance initially. It feels that mocking inner abstract classes is somewhat an edge case (arguably). We could provide an API for this via withSettings() though. From my objective standpoint it feels that it's more important to support constructor arguments than to support mocking inner classes (e.g. I would dare to say that there are more use cases that support the former feature).
I haven't yet looked in the code :)
I reviewed the code. Please don't get disheartened by the amount of review items :) I'm enjoying this conversation immensely and I'm really happy that you will contribute it! It will be a great feature in Mockito.
I've done some refactorings in Mockito regarding mock initialization and I will push it now. Merging your stuff will be hard and I'm sorry for this. I hope that the code after the refactorings will be easier for you to implement the feature ;)
referenced this pull request
Oct 22, 2014
Thanks for explaining the rationale so clearly and patiently! Some further clarifications below in hope that I could sell the idea better. :)
I'll work on the refactoring and merging later.
Agreed. The "opinion" is but one reason though. Another one is that it's not type safe, and would pose a difficulty to tools like IDE later when someone is trying to find references to a constructor.
While Mockito doesn't need to be an opinionated framework, I think it's fair to say that Mockito can choose to only implement a feature that it does better than alternatives.
I imagine if someday someone wants to spy a class with constructor args, my suggestion would be to create a simple subclass wrapper:
This way everything is still nicely compiler and tool friendly. One major difference between Mockito/EasyMock and jMock (at least the earlier versions) is that they are type safe. Supporting arbitrary constructor args using Object takes a step away from the type safety.
There may still be the 20% use case where the constructor args have to be provided dynamically so the inner class approach won't work. I wasn't sure if Mockito should charge itself to support this kind of rare edge cases. Starting from a minimalist perspective, I went with "when in doubt, leave it out".
I'm certainly biased. If that doesn't make the case. I respect your decision. :)
Regarding the pros/cons between constructor-args vs. inner class. My view is quite different. I see "constructor-args" as a crude half-baked product that no one except perhaps framework builders should ever use. There are even ambiguity problems we have to solve. What do we do for the following case?
And what about generics?
Thanks to erasure, Mockito will have no way to figure out that fooSet's static type is
In both cases, the problem is that Mockito has no access to the declared "static" type that the compiler uses and users typically think in terms of. Mockito has access to the runtime types, which, if relied upon, can sometimes cause surprising behavior.
On the other hand, inner class is type safe, tool friendly, no surprise; constructor-args isn't.
Perhaps we can study a few use cases and see how the two contemplated APIs each pans out?
Re: withSettings(): I respect your preference for what the API looks like. So I'll just make sure my concerns are heard.
I have no concern passing these extra state through the settings object internally. The concern is with the overly wide API surface added if we allow users to do:
None of the above make logical sense so could turn out to be surprising to some users. If enclosingInstance is only logical to the spy(Class) method alone, do we want to make it possible to (mis)use to all the other API methods when users hit autocomplete?
Also consider this a slippery slope argument. If the other mock/fake methods later gain their own extra parameters that only make sense to individual API methods (as opposed to the cross-cutting settings today in MockSettings), do we cram them all into the MockSettings object so that users would have to bookmark in their mind things like: "setting 1 should only be used for API 1 and API 2 but not API 3 or API4; setting 2 is good for API 2 only; setting 3 is for API 2 and API 5".
It's contrived. But I feel by adding enclosingInstance to MockSettings, it's the first step toward that direction.
I fear my long reply did a poor job advertising the inner class support. Let me try in a mock FAQ style.
Why do I ever want to test a non-static inner class? Isn't it an implementation detail?
No. You don't.
The inner class support isn't really for testing your production inner classes. Rather, the idea is to reduce most abstract classes with constructor parameters to an inner class enclosed by the test.
Let's look at an example. How do you test the following class?
The constructor has a parameter which is a collaborator. You can
Why inner class? Why not just pass the constructor arguments directly?
A few problems with that approach:
While both you and compiler know for sure
And that's just one parameter.
Sure. But you can throw an exception in case of ambiguity. I'm not sure how often people create potentially ambiguous overloads.
It can happen in practice that at day 1 there is only one constructor and then later on more are added. Despite the best practice of avoiding potentially ambiguous overloads, it occasionally happens, some even for good reasons. For example:
Seems harmless, right?
So overall, we figured that it's better (and easy enough with inner class) for the programmer to explicitly tell us which constructor to use and which parameters and types to pass to it, than Mockito making mostly-right-but-sometimes-surprising guesses.
I admire your energy in selling the idea :). More feedback:
We don't have to support constructor args. We can wait for community feedback. Perhaps nobody asks for it and everybody is happy :)
Let's conclude on the api:
Makes sense? :)
It's not very comparable, right? In
When we do support duck typing, my suggestion would be something similar to EasyMock's ConstructorArgs (http://easymock.org/api/org/easymock/ConstructorArgs.html), which explicitly passes in the Constructor object to avoid constructor selection problem. It's not as handy, yes, but as you said, most users use
But yes, let's leave constructor-args for future iterations.
For now, the remaining gaps between use cases I'm having in mind and the suggested syntax:
This is looking good. I should merge soon.
If the users configures 'useConstructor' I think that mockito should attempt to find matching constructor. If it cannot be found or used, we I'd throw a meaningful exception.
I think we would apply the same strategy as for a)
Yes, so long the exception message is clear and informs how to resolve the problem, etc.
There is a use case we are missing if we throw on private constructor: sometimes you may want to fake a class that didn't expose its constructor, by exposing a static factory instead, or, if the constructor does bad things (like, connect to database).
If we throw on private constructor, there would be absolutely no way to fake it, unless we create another way for user to say "fake it, but don't call constructor".
It's weird if the user called
What do you think of
Hey, I'll allocate some time to it soon. Needed to finish off some
On Wed, Nov 5, 2014 at 4:29 AM, fluentfuture email@example.com
Do you mean changing the @SPY annotation so that it out-of-the box works with AbstractClasses? For sure :) Let's do separate PR/ticket for it.
BTW. I'd like to mention you in the release notes by name. Can you reveal yourself (or configure your github account :)
Hi, thanks for taking over and cleaning it up!
I have a question about the current implementation.
If I'm reading the code correctly,
But it should have mocked the abstract methods rather than CALL_REAL_METHOD on them.
The following test shows what I expect to happen but fails in the current master branch:
In summary, abstract methods by default should use the standard Mockito stubbing. Only the real non-abstract methods are by default invoked. The PartialMockAnswer class in this PR was supposed to implement this logic.
I have a design question to run by you before creating the PR.
Do you think it makes sense to change CALLS_REAL_METHOD directly? It seems useless for it to call abstract methods only to throw an AbstractMethodError.
So perhaps CALLS_REAL_METHOD can be made a bit smarter to delegate to RETURNS_DEFAULTS when it knows it's pointless to call abstract method?
I guess then spy() doesn't need to change.
I think this is a good idea! I'm curious, what's the current behavior when CALLS_REAL_METHOD is used for abstract method? What's the behavior when someone tries to mock an interface and sets the default answer to be CALLS_REAL_METHOD?
Thanks for your insights!
Here's the error I just got from using CALLS_REAL_METHOD on interface methods:
Cool. We can make CALLS_REAL_METHOD smarter I think. Let's also update it's
Thanks a lot for your help!!!!
On Sat, Nov 22, 2014 at 5:31 PM, Ben Yu firstname.lastname@example.org wrote:
Sorry, Szczepan. Two more questions while implementing
I think we agreed that for this code
The question is about
Now with support for abstract class, there is a chance that we could handle all 3 cases consistently: just do
Which approach would you prefer the
Your suggestion makes perfect sense (e.g. simplify, use new feature). BTW.
On Sun, Nov 23, 2014 at 6:11 AM, Ben Yu email@example.com wrote: