Let's take a look at a few method interceptors we'll use to get there (apologies for the large chunk of code):
Here, the MockIntercepted trait is what we'll mix in to the return of methods, e.g. the value of someClass.foo, the first time they are called (or until a stub return value is provided). Because this trait is mixed into a dynamically-generated class, the methods don't actually have implementations anywhere. That's where the StubInterceptor comes in; for the two methods on the MockIntercepted trait, we have predefined return values that give context about which MockInterceptor we're using and which method was stubbed out. These two pieces of information are necessary for thenReturn to set the stub return value.
Lastly, the most important piece is the MockInterceptor itself, which is the method interceptor for the actual mock object. When there is no stub return value set yet, the MockInterceptor will dynamically generate a subclass which mixes in MockIntercepted and uses the StubInterceptor. The subclass carries the context of the method call so we know which method to stub later. The one piece remaining is when the methodReturns map is updated with the stub return values; once it is populated, future calls to the method will always return the stored value! So here's the rest:
The Stubbing object is what actually takes the method and the stub return value and puts them in the methodReturns map. It is returned by the call to when and has context about the mock interceptor and stubbed method because of the MockIntercepted trait we mixed in earlier. The remaining pieces of the implementation are relatively straightforward and included for completeness.
So, to summarize, what does all this code do?
- The mock method dynamically generates a subclass of the mocked class (SomeClass) which uses the MockInterceptor.
- The MockInterceptor dynamically generates a subclass of the return class (StringInt) that mixes in the MockIntercepted trait and uses the StubInterceptor to implement those methods.
- A call to when uses the MockIntercepted trait that was mixed in to create a Stubbing that knows about the corresponding MockInterceptor and the method that was stubbed (foo).
- Calling thenReturn on the Stubbing populates the map maintained by the MockInterceptor to intercept future calls to the method and return the stub value.
As a final note, this implementation is naturally really hacked together in order to keep it short (sort of, anyway). There are numerous details left out and assumptions made that Mockito naturally does a much better job of handling (e.g. I didn't really figure out a robust way to deal with the lack of no-arg constructors in the mocked class), but it touches upon the basic building blocks offered by cglib that Mockito leverages to achieve its magical behavior. Hope this provided some insight into how this awesome library works!
No comments:
Post a Comment