Verifying chained invocations and returned values

1,854 views
Skip to first unread message

MarcosB

unread,
Jun 19, 2013, 12:01:08 PM6/19/13
to moc...@googlegroups.com
Just wanted to get some thoughts on a bit of a problem I've run into with mockito.  The changes to make this happen would be somewhat involved, but I'd be happy to make them. I just wanted to chat about it here first just to make sure I'm not way off-base or crazy.

The basic jist: I've created a chained API which does RPC calls, and I'd like to make sure that said chained API does exactly 1 RPC call.  Spying + verify would work great here, except it completely falls apart with a chained API.

The reason I need a chained API is because there are many permutations of the type of results that can be returned, and currently the API has about 20 methods for all the permutations - with a chained API, it's easy to make this clean.

As an example:
myApi.newLookup().includeSomeAdditionalData().useMaster().execute();

I'd like to be able to do:
verify(myApi, times(1)).newLookup().includeSomeAdditionalData().useMaster().execute();

To do this, 2 things would need to be added to mockito:
  1. For every mock/spy invocation, in addition to calling the answer and recording the invocation, we'd also need to record the return value of said invocation
  2. In verify(), instead of always returning null, we'd return the return value that was recorded for the invocation
This would also be useful for non-chained calls, i.e. for calls that instantiate an object which we want to verify interactions with.

e.g. if my code does something like:
mutation = backend.createMutation();
mutation.doSomething();
mutation.execute();

the changes I suggest allows us to do:
backend = chainedMock.mock(Backend.class);
doStuff(backend);

Mutation createdMutation = chainedMock.verify(backend).createMutation();
verify(createdMutation).doSomething();
verify(createdMutation).execute();

Thoughts?

Eric Lefevre-Ardant

unread,
Jun 20, 2013, 5:36:47 AM6/20/13
to moc...@googlegroups.com
Hm... that sounds quite close to what deep stubs offer. Could you maybe rephrase your idea in terms of what is lacking in deep stubs?



--
You received this message because you are subscribed to the Google Groups "mockito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mockito+u...@googlegroups.com.
To post to this group, send email to moc...@googlegroups.com.
Visit this group at http://groups.google.com/group/mockito.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

MarcosB

unread,
Jun 20, 2013, 2:01:25 PM6/20/13
to moc...@googlegroups.com
This is about verification, not about mocking - yes, deep stubs will do exactly what you mention (as will a fake).  The key here is verifying later.

I'll try to make a better example (apologies).  Here's the code to be tested (this is grossly oversimplified, of course)

class SomeClass {
  SomeResult doSomething();
}

class SomeResult {
  List<String> makeRpcCall();
}

class ToBeTested {
  SomeClass some;
  void doStuff() {
    some.doSomething().makeRpcCall();
  }
}

We can either do deep stubs here, or better yet, a fake.  But how do we verify that we only made a single RPC call? Currently, it can be painful (the above API has a single chain, SomeClass->SomeResult.  But if it's complex RPC call, as it is in my case, we can actually have a chain of 3-4).

My suggestion is to make it so that InvocationOnMock also records the return value of the invocation (whether it was a value from a fake, or a mock from a deep stub answer, etc.).

Then, when we do verify(), instead of returning a null value, we can actually return the recorded value.  This would allow us to verify only 1 rpc was made, by doing this:

SomeClass someClass = spy(new SomeClass());
ToBeTested toTest = new ToBeTested(someClass);
toTest.doStuff();
verify(someClass).doSomething().makeRpcCall();

Remember, I'm talking about verification here - I'm not suggesting adding anything for mocking, only for verification of chained calls.

I can provide sample changes to the mockito codebase if it'll help explain what I'm saying, but I was hoping I could get buy-in before making any code changes.

Eric Lefevre-Ardant

unread,
Jun 24, 2013, 4:55:15 PM6/24/13
to moc...@googlegroups.com
"verify(someClass).doSomething().makeRpcCall()"

Hm... I think I understand what you want, but it seems technically difficult. How would Mockito know that the object returned by doSomething() must be considered for verification?

Imagine that you are writing it in this way (which I'm sure you'll agree is exactly the same):
  Object result = verify(someClass).doSomething();
  result.makeRpcCall();

result.makeRpcCall() might be a perfectly valid call for some later part of your test. How would Mockito know that, in this particular situation, the verification must be made?

The solution, of course, might be:
  Object result = verify(someClass).doSomething();
  verify(result.makeRpcCall());

Or even
  verify(verify(someClass).doSomething()).makeRpcCall());

Which would be rather ugly, but technical feasible, I think.

Of course, I am not a contributor to Mockito, so this could be a completely stupid way to understand the problem.

Marcos Boyington

unread,
Jun 24, 2013, 5:10:24 PM6/24/13
to moc...@googlegroups.com
Hm, you're right of course - I think a new method might be necessary (which wouldn't be too painful, I don't think) - one could continue using verify(), which would simply return the instance from the stub, and do things like:

  Object result = verify(someClass).doSomething();
  verify(result.makeRpcCall());

On the other hand, one could use the new method (e.g. verifyChained()), which would return a special proxy that automatically calls verify() on all calls to it:

  verifyChained(someClass).doSomething().makeRpcCall();

(I actually already have something like this implemented as a wrapper around Mockito, but it would be far easier to implement if Mockito took care of recording returned values).

The proxy is basically a cglib proxy which intercepts all method calls and does this:

result = method.invoke(Mockito.verify(mock, this), args);


--
You received this message because you are subscribed to a topic in the Google Groups "mockito" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/mockito/zwPcFC1GE-w/unsubscribe.
To unsubscribe from this group and all its topics, send an email to mockito+u...@googlegroups.com.

To post to this group, send email to moc...@googlegroups.com.

MarcosB

unread,
Jul 2, 2013, 4:42:30 PM7/2/13
to moc...@googlegroups.com
Any additional thoughts on this? Would this be something which the mockito community could see being useful, and would be willing to have a look at some code changes to the mockito core to do as I suggest?
To unsubscribe from this group and stop receiving emails from it, send an email to mockito+unsubscribe@googlegroups.com.

To post to this group, send email to moc...@googlegroups.com.

Visit this group at http://groups.google.com/group/mockito.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

--
You received this message because you are subscribed to a topic in the Google Groups "mockito" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/mockito/zwPcFC1GE-w/unsubscribe.
To unsubscribe from this group and all its topics, send an email to mockito+unsubscribe@googlegroups.com.

To post to this group, send email to moc...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages