140

Given the following Mockito statement:

when(mock.method()).thenReturn(someValue);

How does Mockito go about creating a proxying something for a mock, given that the mock.method() statement will pass the return value to when()? I imagine that this uses some CGLib stuff, but would be interested to know how this is technically done.

Paul Morie
  • 15,528
  • 9
  • 52
  • 57
marchaos
  • 3,316
  • 4
  • 26
  • 35

2 Answers2

139

The short answer is that in your example, the result of mock.method() will be a type-appropriate empty value; mockito uses indirection via proxying, method interception, and a shared instance of the MockingProgress class in order to determine whether an invocation of a method on a mock is for stubbing or replay of an existing stubbed behavior rather than passing information about stubbing via the return value of a mocked method.

A mini-analysis in a couple of minutes looking at the mockito code is as follows. Note, this is a very rough description - there are a lot of details in play here. I suggest that you check out the source on github yourself.

First, when you mock a class using the mock method of the Mockito class, this is essentially what happens:

  1. Mockito.mock delegates to org.mockito.internal.MockitoCore.mock, passing the default mock settings as a parameter.
  2. MockitoCore.mock delegates to org.mockito.internal.util.MockUtil.createMock
  3. The MockUtil class uses the ClassPathLoader class to get an instance of MockMaker to use to create the mock. By default, the CgLibMockMaker class is used.
  4. CgLibMockMaker uses a class borrowed from JMock, ClassImposterizer that handles creating the mock. The key pieces of the 'mockito magic' used are the MethodInterceptor used to create the mock: the mockito MethodInterceptorFilter, and a chain of MockHandler instances, including an instance of MockHandlerImpl. The method interceptor passes invocations to MockHandlerImpl instance, which implements the business logic that should be applied when a method is invoked on a mock (ie, searching to see if an answer is recorded already, determining if the invocation represents a new stub, etc. The default state is that if a stub is not already registered for the method being invoked, a type-appropriate empty value is returned.

Now, let's look at the code in your example:

when(mock.method()).thenReturn(someValue)

Here is the order that this code will execute in:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

The key to understanding what is going on is what happens when the method on the mock is invoked: the method interceptor is passed information about the method invocation, and delegates to its chain of MockHandler instances, which eventually delegate to MockHandlerImpl#handle. During MockHandlerImpl#handle, the mock handler creates an instance of OngoingStubbingImpl and passes it to the shared MockingProgress instance.

When the when method is invoked after the invocation of method(), it delegates to MockitoCore.when, which calls the stub() method of the same class. This method unpacks the ongoing stubbing from the shared MockingProgress instance that the mocked method() invocation wrote into, and returns it. Then thenReturn method is then called on the OngoingStubbing instance.

Paul Morie
  • 15,528
  • 9
  • 52
  • 57
  • 1
    Thanks for the detailed response. Another question - you mention that "when method is invoked after the invocation of method()" - how does it know that the invocation of when() is the very next invocation (or wraps) the invocation of method()? Hope that makes sense. – marchaos Jan 22 '13 at 11:06
  • @marchaos It doesn't know. With the `when(mock.method()).thenXyz(...)` syntax, `mock.method()` gets executed in "replay" mode, not in "stubbing" mode. Normally, this execution of `mock.method()` has no effect, so later when `thenXyz(...)` (`thenReturn`, `thenThrow`, `thenAnswer`, etc) gets executed, it goes into "stubbing" mode and then records the desired result for that method call. – Rogério Jan 22 '13 at 14:44
  • 1
    Rogerio, it's actually a little more subtle than that - mockito doesn't have explicit stubbing and replay modes. I'll edit my answer later so it's more clear. – Paul Morie Jan 22 '13 at 15:08
  • I would sum that in short, it is easier to intercept a method call within another method with CGLIB or Javassist that to intercept, say, "if" operator. – Infeligo Sep 19 '14 at 10:07
  • I haven't yet massaged my description here, but I haven't forgotten about it either. FYI. – Paul Morie Jan 05 '15 at 17:25
  • Ingenious. At first sight, for this to work I would assume that `Mockito.when()` needed lazy evaluation of its parameter to work. The call `mock.method()` would be suspended and passed to `Mockito.when()`), then `Mockito.when()` would need to inspect the suspended op, discover the `mock` instance and the name of the `method` method call, then set up the forthcoming definition of the expected response of `method()`. The JVM would need several capabilities and runtime code introspection features that it doesn't have. But it's all solved by having a static `MockingProgress` housekeeping element. – David Tonhofer Oct 07 '17 at 22:37
  • It's really confusing at first that the parameter of `when()` is totally ignored. `when(mock.method()).thenReturn(42);` is equivalent to `mock.method();when(null).thenReturn(42);`. The place between the a parentheses of `when()` is just a practically good spot for placing the mock call. – Dávid Horváth Oct 28 '19 at 02:04
44

The short answer is, behind the scenes, Mockito uses some kind of global variables/storage to save information of method stub building steps(invocation of method(), when(), thenReturn() in your example), so that eventually it can build up a map on what should be returned when what is called on what param.

I found this article very helpful: Explanation how proxy based Mock Frameworks work (http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html). The author implemented a demonstration Mocking framework, which I found a very good resource for people who want to figure out how these Mocking frameworks work.

In my opinion, it's a typical usage of Anti-Pattern. Normally we should avoid 'side effect' when we implement a method, meaning the method should accept input and do some calculation and return the result - nothing else changed besides that. But Mockito just violates that rule on purpose. Its methods store a bunch of info besides returning the result: Mockito.anyString(), mockInstance.method(), when(), thenReturn, they all have special 'side effect'. That's also why the framework looks like a magic at the first glance - we usually don't write code like that. However, in the mocking framework case, this anti-pattern design is a great design, since it leads to very simple API.

Jacob Wu
  • 868
  • 9
  • 11
  • 5
    Excellent link. _The genius behind this is: The very simple API which makes the whole thing looks like very nice. Another great decision is that the when() method uses generics so that the thenReturn() method is type safe._ – David Tonhofer Oct 07 '17 at 22:54
  • 2
    I consider this the better answer. In contrast to the other answer, it clearly explains the concepts of the mocking process instead of the control flow through concrete code. I agree, excellent link. – mihca Oct 23 '19 at 12:33