You are reading a translation of an old blog post published on my previous blog in French.
We decided during the main conference that we should use JUnit 4 and Mockito because we think they are the future of TDD and mocking in Java.
— Dan North, creator of BDD
The elegant syntax of Mockito probably explains its huge success in the Java ecosystem. How Mockito manages to keep our tests readable? What the code looks like behind this API? Let’s find it out.
Mockito is published under MIT license. The code presented in this article has been simplified for obvious reasons and must not be used outside this learning context. This article is based on the last version of Mockito (1.10.14) at the publication time.
A First Example
public interface Registry{ Object lookup(String name);}
This basic interface follows the pattern Registry. To limit the number of calls to the registry, we decide to cache results using the pattern Decorator:
public class RegistryCacheDecorator implements Registry{
private Registry decoratedRegistry; private Map<String, Object> cache = new HashMap<String, Object>();
public RegistryCacheDecorator(Registry registry) { this.decoratedRegistry = registry; }
public Object lookup(String name) { if (!cache.containsKey(name)) { cache.put(name, decoratedRegistry.lookup(name)); } return cache.get(name); }
}
How to test this class to ensure the registry has not been called twice for the same argument? We need a Test Double for that, in particular, a mock. (See this post by Martin Fowler to understand the differences between the types of Test Double).
import static org.mockito.Mockito.*;
public class RegistryCacheDecoratorUnitTest{
private RegistryCacheDecorator decorator; private Registry registry;
@Before public void before() { registry = mock(Registry.class);
decorator = new RegistryCacheDecorator(registry); }
@Test public void registryShouldOnlyBeCalledOnceForTheSameName() { // Given when(registry.lookup(anyString())).thenReturn(new BasicDataSource());
// When decorator.lookup("datasource"); decorator.lookup("datasource");
// Then verify(registry, times(1)).lookup("datasource"); }}
- 1
- We instantiate a new mock for each test.
- 2
- We configure this mock to associate an instance of
DataSource
with the name"dataSource"
. - 3
- We check that the registry has been called once with this name.
When running the class, the test succeeds. The goal is now to remove all Mockito import statements and to rewrite the minimal code to make the test successful again. We will try to match as much as possible the original code of Mockito, when naming classes, when writing algorithms, etc., even if some compromises will be necessary to keep this post relatively short.
Let’s Go!
Following the removal of imports, the code no longer compiles. We start by defining the signature of missing methods:
public class Mockito{
public static <T> T mock(Class<T> classToMock) { return null; // Implemented in the section "mock" }
public static <T> OngoingStubbing<T> when(T methodCall) { return null; // Implemented in the section "when" }
public static <T> T verify(T mock, Object aDefinir) { return mock; // Implemented in the section "verify" }
}
The code still doesn’t compile due to a reference to the type OngoingStubbing
. The following code will fix this problem:
public interface OngoingStubbing<T>{ OngoingStubbing<T> thenReturn(T value);}
You can ignore this interface for now. It will be introduced later.
mock
Creating a mock means creating a proxy, an object that looks like the actual instance but whose behavior is preprogrammed. Since Java 1.3, we can use Dynamic Proxies to create dynamically a new class but only from an interface. This restriction is often problematic in practice, and most projects like Spring, Hibernate, or Mockito use a different library that manipulates the bytecode. The most famous library is cglib. This library works by extending the class, a more flexible approach (the class must just not be declared final
).
Mockito abstracts cglib behind two interfaces: MockMaker
and MockHandler
.
public interface MockMaker {
<T> T createMock(Class<T> classToMock, MockHandler handler);}
An implementation of MockMaker
is responsible for instantiating a proxy so that each call is delegated to the instance of the class MockHandler
:
public interface MockHandler{ Object handle(Invocation invocation) throws Throwable;}
The class Invocation
is a simple type grouping various properties related to a single method call:
public class Invocation {
private final Object mock; private final Method method; private final Object[] arguments; private final MethodProxy methodProxy; // Specific cglib
public Invocation(Object mock, Method method, Object[] args, MethodProxy methodProxy) { this.method = method; this.mock = mock; this.methodProxy = methodProxy; this.arguments = args; }
public Object getMock() { return mock; }
public Method getMethod() { return method; }
public Object[] getArguments() { return arguments; }
}
cglib/ASM/Objenesis
We are approaching the low-level details of how Mockito is working. The code is relatively easy to understand due to the cglib API that relies internally on the API of another low-level library, ASM. Here is the implementation of MockMaker
:
import org.mockito.cglib.core.CodeGenerationException;import org.mockito.cglib.proxy.Callback;import org.mockito.cglib.proxy.Enhancer;import org.mockito.cglib.proxy.Factory;import org.mockito.cglib.proxy.MethodInterceptor;import org.mockito.exceptions.base.MockitoException;import org.objenesis.ObjenesisStd;
public class CglibMockMaker implements MockMaker {
public <T> T createMock(Class<T> mockedType, MockHandler handler) {
try { MethodInterceptor interceptor = new MethodInterceptorFilter(handler);
Class<Factory> proxyClass = createProxyClass(mockedType); Object proxyInstance = createProxy(proxyClass, interceptor); return mockedType.cast(proxyInstance); } catch (ClassCastException cce) { throw new MockitoException( "Exception occurred while creating the mockito proxy", cce); }
}
public Class<Factory> createProxyClass(Class<?> mockedType) { Enhancer enhancer = new Enhancer(); enhancer.setUseFactory(true); enhancer.setSuperclass(mockedType); enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class});
try { return enhancer.createClass(); } catch (CodeGenerationException e) { throw new MockitoException( "Mockito cannot mock this class: " + mockedType); } }
private Object createProxy(Class<Factory> proxyClass, MethodInterceptor interceptor) { ObjenesisStd objenesis = new ObjenesisStd(); Factory proxy = objenesis.newInstance(proxyClass); proxy.setCallbacks(new Callback[] {interceptor}); return proxy; }
}
Don’t panic if you do not understand everything. The code is not as complex as it may seem. Let’s go through the code step by step.
We start by creating an instance of
Enhancer
, the main class in cglib, responsible for creating new classes dynamically.Enhancer enhancer = new Enhancer();We then describe what our mock must look like:
enhancer.setUseFactory(true);enhancer.setSuperclass(mockedType);enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class});The most important line is the second one, where we specify the class of our mock. To understand the first line, you need to know that all classes generated by cglib implement by default the interface
Factory
. This interface allows, for example, to switch the callback. The methodsetUseFactory
allows to disable this feature, but we are simply setting the default value and is therefore useless. The last line defines the kind of callback we are going to use. Several ones are available such asFixedValue
to return the same value for every method call. The most flexible callback isMethodInterceptor
that gives us full control of all call metadata to determine the result.We create the
Class
object that will be used to instantiate our mock.return enhancer.createClass();We call the method
newInstance
to create a new instance from the objectClass
we got just before:Class<T> cls = ...:return cls.newInstance();
This method requires a default constructor. This restriction may cause problems in some cases.
Imagine that the class to mock inherits another class:
public class Parent { public Parent() { // will be executed by Child.class.newInstance()... }
}
public class Child extends Parent {
}
Depending on what the parent constructor does, the result may be problematic.
Yes, by using the library Objenesis. Again, this library works by manipulating the bytecode that may differ based on the version of the JVM, the vendor, … (See the class StdInstantiatorStrategy
if you are curious).
With this new knowledge, we can go back to the class MockHandler
:
MethodInterceptor interceptor = new MethodInterceptorFilter(handler);ObjenesisStd objenesis = new ObjenesisStd();Factory proxy = objenesis.newInstance(proxyClass);proxy.setCallbacks(new Callback[] {interceptor});return proxy;
Objenesis creates a new instance of our dynamic proxy. Our mock is born. We associate it to an instance of MethodInterceptorFilter
to connect cglib with our MockHandler
.
import org.mockito.cglib.proxy.MethodInterceptor;
public static class MethodInterceptorFilter implements MethodInterceptor {
private final MockHandler handler;
public MethodInterceptorFilter(MockHandler handler) { this.handler = handler; }
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Invocation invocation = new Invocation(proxy, method, args, methodProxy); return handler.handle(invocation); }}
Before closing this first section, you may have noticed that Mockito repackages cglib (and ASM):
import org.mockito.cglib.proxy.Enhancer;
It also raises another question.
To know more about CGlib, I recommend this excellent article to fix the missing official documentation.
when
Even if we are done with low-level bytecode manipulation, the following sections are not necessarily more straightforward. The Mockito API is simple to use but not necessarily to write.
A first glimpse…
when(registry.lookup(anyString())).thenReturn(new Object());
When executing this line of code:
- The method
anyString
is called first. We memorize the use of anArgumentMatcher
(in a kind of global variable). - The method
registry#lookup(String)
is then called (in fact, we call the interceptor of our mock). We memorize the invocation still using the global variable. - The method
when
is called. We know at this moment that we are configuring our mock. - The method
thenReturn
is called last. We exploit everything we memorized before, we save the expected result to return it when the mock will be really called during the test.
Let’s start with the global variable. This variable is an instance of the class MockingProgress
:
public class MockingProgress{
/** Global variable */ public static MockingProgress INSTANCE = new MockingProgress();
private final List<Matcher> matcherStack = new ArrayList<Matcher>(); private OngoingStubbing ongoingStubbing;
/** Called by every ArgumentMatcher (anyString, eq, ...) */ public void reportMatcher(Matcher matcher) { matcherStack.add(matcher); }
/** Called when the mock is executed during the when() */ public void reportOngoingStubbing(OngoingStubbing ongoingStubbing) { this.ongoingStubbing = ongoingStubbing; }
/** Called by the method when() to confirm the stubbing */ public void stubbingStarted() {
}
/** Returns the memorized ArgumentMatchers */ public List<Matcher> pullMatchers() { if (matcherStack.isEmpty()) { return Collections.emptyList(); }
List<Matcher> matchers = new ArrayList<Matcher>(matcherStack); matcherStack.clear(); return matchers; }
/** * Called by the method when() to retrieve the instance * to return to chain the methods. */ public OngoingStubbing pullOngoingStubbing() { OngoingStubbing temp = ongoingStubbing; ongoingStubbing = null; return temp; }
}
This class will be modified in the last part. The code appears complex but the logic is simple: every time we know more about our position in the code, we communicate this position to this class, so we will be able to retrieve everything later.
Let’s talk about something more simple: the instances of ArgumentMatcher
, based on the excellent library Hamcrest:
import org.hamcrest.BaseMatcher;
public abstract class ArgumentMatcher<T> extends BaseMatcher<T> {
public abstract boolean matches(Object argument);
}
Mockito offers many matchers. For our example, only two matchers are necessary:
public class Any<T> extends ArgumentMatcher<T> {
@Override public boolean matches(Object actual) { return true; // all values are OK }
public void describeTo(Description description) { description.appendText("<any>"); }}
public class Equals<T> extends ArgumentMatcher<T> {
private final Object wanted;
public Equals(Object wanted) { this.wanted = wanted; }
@Override public boolean matches(Object actual) { return wanted == actual; }
public void describeTo(Description description) { description.appendText("<eq>"); }}
Their use requires that we call a factory method instead. This factory serves two purposes: communicates the matcher has been used and returns the right type for the code to compile (Note: instantiating a matcher directly instead of using anyString()
would not compile):
public class Matchers{
public static String anyString() { MockingProgress.INSTANCE.reportMatcher(new Any()); return ""; }
public static <T> T eq(T value) { MockingProgress.INSTANCE.reportMatcher(new Equals(value)); return value; }
}
Before moving to the last section, we need to introduce the class InvocationMatcher
that we will encounter many times. This class associates an instance of Invocation
(a method call) with the list of used matchers. Even if we receive the arguments in the object Invocation
, the matchers are not present as attested by the previous code (anyString
returns for example an empty string). Here is the code of this class:
public class InvocationMatcher {
private final Invocation invocation; private final List<Matcher> matchers;
public InvocationMatcher(Invocation invocation, List<Matcher> matchers) { this.invocation = invocation; if (matchers.isEmpty()) { this.matchers = argumentsToMatchers(invocation.getArguments()); } else { this.matchers = matchers; } }
public static List<Matcher> argumentsToMatchers(Object[] arguments) { List<Matcher> matchers = new ArrayList<Matcher>(arguments.length); for (Object arg : arguments) { matchers.add(new Equals(arg)); } return matchers; }
public Invocation getInvocation() { return this.invocation; }
public List<Matcher> getMatchers() { return this.matchers; }
public boolean matches(Invocation actual) { return invocation.getMock() == actual.getMock() && hasSameMethod(actual) && hasMatchingArguments(this, actual); }
private boolean hasSameMethod(Invocation candidate) { Method m1 = this.getInvocation().getMethod(); Method m2 = candidate.getMethod(); return m1.equals(m2); }
private boolean hasMatchingArguments(InvocationMatcher invocationMatcher, Invocation actual) { Object[] actualArgs = actual.getArguments(); if (actualArgs.length != invocationMatcher.getMatchers().size()) { return false; } for (int i = 0; i < actualArgs.length; i++) { if (!invocationMatcher.getMatchers().get(i).matches(actualArgs[i])) { return false; } } return true; }
}
- 1
- When calling a method on a mock (when using
when
orverify
), Mockito requires only literal values/variables, or only matchers. Under the hood, Mockito makes sure to only work with matchers. This is the role of the methodargumentsToMatchers
.
We still have a few classes to introduce like the class InvocationContainer
. Unlike MockingProgress
, shared between all mocks and all tests, every mock gets its own instance of InvocationContainer
. This class keeps track of all stubbed invocations, which are all invocations using when
to program the mock but also all invocations during the test execution that will be very useful to check assertions (verify
).
public class InvocationContainer{
private final Map<InvocationMatcher, Answer> stubbed = new HashMap<InvocationMatcher, Answer>(); private InvocationMatcher invocationForStubbing; private LinkedList<Invocation> registeredInvocations = new LinkedList<Invocation>();
public void setInvocationForPotentialStubbing( InvocationMatcher invocationMatcher) { registeredInvocations.add(invocationMatcher.getInvocation()); invocationForStubbing = invocationMatcher; }
public void addAnswer(Answer answer) { registeredInvocations.removeLast(); stubbed.put(invocationForStubbing, answer); invocationForStubbing = null; }
public List<Invocation> getInvocations() { return registeredInvocations; }
public Answer findAnswerFor(Invocation invocation) { for (Entry<InvocationMatcher, Answer> eachEntry : stubbed.entrySet()) { InvocationMatcher eachInvocationMatcher = eachEntry.getKey(); Answer eachAnswer = eachEntry.getValue(); if (eachInvocationMatcher.matches(invocation)) { return eachAnswer; } }
return null; }
}
- 1
stubbed
contains all method calls recorded usingwhen
.- 2
invocationForStubbing
contains the current method call. We ignore for now the context (when
, normal call, orverify
).- 3
registeredInvocations
contains all the effective method calls (calls happening outside a call towhen
orverify
).- 4
- This method is called every time we call a method on the mock. It can happen when exercising the mock or at the end of a test when calling
verify
. In the first case, the methodaddAnwser
will be called just after (by the methodthenReturn
for example) to allow us to record this invocation in the list of stubbed invocations. For the second case, it is our only chance to take note of the call as there would not later method call likethenReturn
to confirm the invocation context. Therefore, we add the invocation to the list of effective invocations, and we will remove it if a method likeaddAnswer
is called just after.
The answers are represented by the interface Answer
. They may describe a return value (thenReturn
), an exception to propagate (thenThrow
), etc.
public interface Answer<T> { T answer(Invocation invocation) throws Throwable;}
public class Returns implements Answer<Object> {
private final Object value;
public Returns(Object value) { this.value = value; }
public Object answer(Invocation invocation) throws Throwable { return value; }
}
public class ThrowsException implements Answer<Object> {
private final Throwable throwable;
public ThrowsException(Throwable throwable) { this.throwable = throwable; }
public Object answer(Invocation invocation) throws Throwable { throw throwable; }
}
The recording of all answers is done by one of the first abstractions we introduced in this article—the class OngoingStubbing
that is returned by the method when
. Here is the new definition of this class:
public static class OngoingStubbing<T>{
private final InvocationContainer invocationContainer;
public OngoingStubbing(InvocationContainer invocationContainer) { this.invocationContainer = invocationContainer; }
public OngoingStubbing<T> thenReturn(T value) { return thenAnswer(new Returns(value)); }
public OngoingStubbing<T> thenThrow(Throwable throwable) { return thenAnswer(new ThrowsException(throwable)); }
public OngoingStubbing<T> thenAnswer(Answer<?> answer) { invocationContainer.addAnswer(answer); return this; }}
This second section is almost complete. We just need to assemble the different classes in MockHandler
, the interceptor called on every method execution on our mock:
public class MockHandlerImpl<T> implements MockHandler{
private MockingProgress mockingProgress = MockingProgress.INSTANCE; private InvocationContainer invocationContainer;
public MockHandlerImpl() { this.invocationContainer = new InvocationContainer(); }
public Object handle(Invocation invocation) throws Throwable {
List<Matcher> lastMatchers = mockingProgress.pullMatchers(); InvocationMatcher invocationWithMatchers = new InvocationMatcher(invocation, lastMatchers);
invocationContainer.setInvocationForPotentialStubbing( invocationWithMatchers); OngoingStubbing<T> ongoingStubbing = new OngoingStubbing<T>(invocationContainer); mockingProgress.reportOngoingStubbing(ongoingStubbing);
// look for existing answers for this invocation Answer answer = invocationContainer.findAnswerFor(invocation);
if (answer == null) { // when? return null; } else { // called by the test return answer.answer(invocation); } }
}
- 1
- Each
MockHandler
is associated with an instance of a mock. It’s the best place to create the instance ofInvocationContainer
. - 2
- We dequeue all matchers to create an instance of
InvocationMatcher
. - 3
- We record a possible stubbing (will be confirmed later, or not).
- 4
- If an answer has already been programmed, we simply return it.
We must not forget to update our initial implementation of the method when
:
public class Mockito{ // ...
public static <T> OngoingStubbing<T> when(T methodCall) { mockingProgress.stubbingStarted(); return mockingProgress.pullOngoingStubbing(); }
}
verify
Compared to the previous section, this last one will be far less challenging.
A first glimpse…
verify(registry, times(1)).lookup(anyString());
When executing this line of code:
- The method
times
is called. This factory simply creates an instance ofVerificationMode
. - The method
verify
is called. We memorize the expected result passed in parameter (times(1)
) in our global objectMockingProgress
. - The method
anyString()
is called. As usual, we memorize the matchers for later. - The method
registry#lookup(String)
is called again. We end up in theMockHandler
implementation where the verification is really done. We check the matchers and look for a matching invocation.
Let’s start by introducing the interface VerificationMode
:
public interface VerificationMode{ void verify(VerificationData data);}
As for matchers, several implementations are available. Only times
is used by our example:
public class Times implements VerificationMode{
final int wantedCount;
public Times(int wantedNumberOfInvocations) { this.wantedCount = wantedNumberOfInvocations; }
public void verify(VerificationData data) { int actualCount = 0; for (Invocation eachInvocation : data.getAllInvocations()) { if (data.getWanted().matches(eachInvocation)) { actualCount++; } } if (actualCount != wantedCount) { throw new MockitoAssertionError( "Actual: " + actualCount + ", expected: " + wantedCount); } }
}
The verification is trivial by using the content of VerificationData
containing all effective invocations, plus the invocations triggered by the verify
functions. We just have to find the matching invocations and compare them with the expected number of invocations.
public class VerificationData{
private final InvocationMatcher wanted; private final InvocationContainer invocations;
public VerificationData(InvocationContainer invocations, InvocationMatcher wanted) { this.invocations = invocations; this.wanted = wanted; }
public List<Invocation> getAllInvocations() { return invocations.getInvocations(); }
public InvocationMatcher getWanted() { return wanted; }}
We need to revise our implementation of the method verify
:
public class Mockito{
// …
public static <T> T verify(T mock, VerificationMode mode) { mockingProgress.verificationStarted(mode); return mock; }
}
And MockingProgress
too:
public class MockingProgress{
// …
private VerificationMode verificationMode;
public void verificationStarted(VerificationMode verify) { ongoingStubbing = null; verificationMode = verify; }
public VerificationMode pullVerificationMode() { VerificationMode temp = verificationMode; verificationMode = null; return temp; }}
The last change occurs in MockHandler
where the main logic resides:
public static class MockHandlerImpl<T> implements MockHandler{
private MockingProgress mockingProgress = MockingProgress.INSTANCE; private InvocationContainer invocationContainer;
public MockHandlerImpl() { this.invocationContainer = new InvocationContainer(); }
public Object handle(Invocation invocation) throws Throwable {
VerificationMode verificationMode = mockingProgress.pullVerificationMode();
List<Matcher> lastMatchers = mockingProgress.pullMatchers(); InvocationMatcher invocationWithMatchers = new InvocationMatcher(invocation, lastMatchers);
if (verificationMode != null) { // verify? VerificationData data = createVerificationData( invocationContainer, invocationWithMatchers); verificationMode.verify(data); return null; }
invocationContainer.setInvocationForPotentialStubbing(invocationWithMatchers); OngoingStubbing<T> ongoingStubbing = new OngoingStubbing<T>(invocationContainer); mockingProgress.reportOngoingStubbing(ongoingStubbing);
// look for the existing answers for this invocation Answer answer = invocationContainer.findAnswerFor(invocation);
if (answer == null) { // when? return null; } else { // called by the test return answer.answer(invocation); } }
private VerificationData createVerificationData( InvocationContainer invocationContainer, InvocationMatcher invocationMatcher) { return new VerificationData(invocationContainer, invocationMatcher); }}
Congratulations, you have just written a minimal but operational version of Mockito in less than 500 lines. The complete source code is available here.
It’s Not Over!
Bonus: Multithreading
Most classes are used by a single mock instance. For example, each mock has its own instance of MockHandler
. The only class to synchronize is the class MockingProgress
, used as the global placeholder to support the flexibility of the Mockito API. Using the Java class ThreadLocal
, making this class thread-safe is trivial:
public class ThreadSafeMockingProgress {
private static final ThreadLocal<MockingProgress> mockingProgress = new ThreadLocal<MockingProgress>();
static MockingProgress threadSafely() { if (mockingProgress.get() == null) { mockingProgress.set(new MockingProgress()); } return mockingProgress.get(); }
// ...
public void verificationStarted(VerificationMode verify) { threadSafely().verificationStarted(verify); }
public VerificationMode pullVerificationMode() { return threadSafely().pullVerificationMode(); }
}
Each method retrieves systematically the instance associated with the current thread using the method get
defined on ThreadLocal
. We just have to replace all references to this class:
MockingProgress mockingProgress = MockingProgress.INSTANCE;
By:
MockingProgress mockingProgress = new ThreadSafeMockingProgress();
That’s done!
Bonus: Error Management
Reporting errors use without surprise Java exceptions, but these exceptions are not thrown directly when an error is detected. Mockito delegates this responsibility to the class Reporter
, which centralizes all possible errors. A specific method exists for every kind of error. Here is an extract of this class:
package org.mockito.exceptions;
public class Reporter {
public void incorrectUseOfApi() { throw new MockitoException(join( "Incorrect use of API detected here:", new LocationImpl(), "", "You probably stored a reference to OngoingStubbing ...", "Examples of correct usage:", " when(mock.isOk()).thenReturn(true).thenThrow(exception);", " when(mock.isOk()).thenReturn(true, false).thenThrow(exception);", "" )); }
public void notAMockPassedToWhenMethod() { throw new NotAMockException(join( "Argument passed to when() is not a mock!", "Example of correct stubbing:", " doThrow(new RuntimeException()).when(mock).someMethod();" )); }
public void invalidUseOfMatchers(int expectedMatchersCount, List<LocalizedMatcher> recordedMatchers) { throw new InvalidUseOfMatchersException(join( "Invalid use of argument matchers!", expectedMatchersCount + " matchers expected, " + recordedMatchers.size() + " recorded:" + locationsOf(recordedMatchers), "", "This exception may occur if matchers are combined with raw values:", " //incorrect:", " someMethod(anyObject(), \"raw String\");", "When using matchers, all arguments have to be provided by matchers.", "For example:", " //correct:", " someMethod(anyObject(), eq(\"String by matcher\"));", "", "For more info see javadoc for Matchers class.", "" )); }
// …}
The main advantage of this class is to centralize all error messages in the same place to make sure their formatting is consistent. Here is an example of use:
new Reporter().invalidUseOfMatchers(...);
- An API can be simple to use but not so simple to implement.
- It is possible to instantiate a class in Java without calling the constructor using libraries like Objenesis.
- To create a proxy for a concrete class in Java, we have to use bytecode manipulation using libraries like Cglib or Javassist.
- Cglib is still ubiquitous in many popular frameworks, but most are migrating to Javassist.
- Using
ThreadLocal
gives access to a global context for every thread of an application.
The rewrite of Mockito is missing many great features. Here are a few examples of some features omitted that you may find interesting to inspect:
- Mockito supports the verification
inOrder
to make sure two mocks have been called in a precise order. We have seen thatInvocationContainer
is associated with a single mock. How Mockito manages to support this use case? + Hint: The classInvocationImpl
contains an attributesequenceNumber
. - Mockito supports the verification
verifyZeroInteractions
, which as its name suggests, makes sure no interaction besides the ones defined occurred. How does it work? + Hint: The classInvocationImpl
contains an attributeverified
. - Mockito supports, for a single call to the method
when
, to chain several calls to the methodsthenReturn
,thenThrow
, etc., that will match the result of the first execution, then the second, and so on. + Hint: CompareOngoingStubbingImpl
withConsecutiveStubbing
.