Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito Internals

I'm trying to understand how Mockito's internals function. So far the code has been difficult for me to understand, and I'm looking for a high-level survey of the fundamental workings of Mockito.

Mockito @ GrepCode

I've written some sample code to demonstrate my current understanding:

class C {
    String s;
    public void getS() { return s; }
    // ...
}

C cm = mock( C.class);
when( cm.method() ).thenReturn( "string value");

As far as I can tell, the 'mock' method is only seeing the return value for cm.getS(). How can it know what the name of the method is (in order to stub it)? Also, how can it know the arguments passed to the method?

The mockito API method calls an internal object's method:

// org.mockito.Mockito
public static <T> OngoingStubbing<T> when(T methodCall) {
    return MOCKITO_CORE.when(methodCall);
}

I've followed the method invocations into several different abstractions and classes and objects, but the code is so decoupled that it is difficult to understand this way.

//  org.mockito.internal.MockitoCore
public <T> OngoingStubbing<T> when(T methodCall) {
    mockingProgress.stubbingStarted();
    return (OngoingStubbing) stub();
}

So if anyone understands the internals or has a link to a discussion/blog post, please share :)

like image 720
xst Avatar asked Mar 28 '13 22:03

xst


1 Answers

(Sorry this got long. TL;DR: Mockito records the method call behind the scenes.)

C cm = mock(C.class);

At this point, you may think that cm is an instance of C...and you would be wrong. Instead, cm is an instance of a proxy object Mockito writes that implements C (and thus can be assigned to fields/variables of type C) but records everything you ask for and behaves the way you stub it to.

Let's write a mock class manually...and give it one more method, let's say int add(int a, int b), which adds a and b in the actual class.

class MockC extends C {
  int returnValue;

  @Override int add(int a, int b) {
    return returnValue;
  }
}

There! Now whenever you call add, it won't add the two numbers, but instead just return the single return value. Makes sense. But what if you want to verify the calls later?

class MockC extends C {
  List<Object[]> parameterValues = new ArrayList<>();
  int returnValue;

  @Override int add(int a, int b) {
    parameterValues.add(new Object[] { a, b });
    return returnValue;
  }
}

So now you can check the parameterValues list and make sure it was called as expected.

Here's the thing: Mockito generates a proxy using CGLIB that acts like MockC automatically, keeping all of the interactions and return values in one big static list. The list is called RegisteredInvocations, and instead of an Object[] every method call for every mock is an Invocation, but the idea is the same.

To understand a little more about RegisteredInvocations and why the removeLast method it exposes is so important, read the code in InvocationContainer. Because Mockito records every call, it will naturally record the interaction contained within when. As soon as Mockito sees when, it removes the last recorded interaction (InvocationContainerImpl.java line 45) and uses it as the template for your stubbing--reading the argument values from the Invocation object itself.

That takes care of most of it, except for argument matchers like eq and any: Turns out that those are just kept on a fancy stack called ArgumentMatcherStorage. The when call checks how many matchers are on the stack: For the add example, zero matchers tells Mockito to infer equality with every recorded argument, and two matchers tells Mockito to pop those off the stack and use those. Just one matcher means Mockito can't tell which integer you're trying to match and throws the often-confusing InvalidUseOfMatchersException, which is why when using matchers you need to match every argument if you match any at all.

Hope that helps!

EDIT: This answer describes how the when method works in more technical detail.

like image 86
Jeff Bowman Avatar answered Oct 05 '22 09:10

Jeff Bowman