Introduction
- So I am building my own little project, which you can read about HERE and I have made the decision to use as few libraries as possible. Now that I am doing some testing I need some mock objects, which means I have to try to recreate Mockito. So this series will be me recreating Mockito(a much simpler version) the best I can. It won't be pretty but it will work
GitHub
- HERE is the code
Recap
- In the last tutorial we got our code to be called when the Junit test engine reached the
Test instance postprocessing
extension point. However, our code really only had one use case. So in this tutorial we will be deleting all the previous code inside thepostProcessTestInstance
method and turning the@Gucci
annotation into a automatic dependency injector. Not quite a mocked object but we are getting closer.
ClassLoader
- If you are following along in this series, you can now delete all the previous code inside the
postProcessTestInstance
and paste in this code:
ClassLoader classLoader = obj.getClass().getClassLoader();
if(classLoader == null){
classLoader = ClassLoader.getSystemClassLoader();
}
- This might seem a little strange, so lets first talk about what a class loader is and what it actually does.
- A class loader object is a normal object and it gets stored on the heap like everyone else. However, it allows us to dynamically extend our Java program. So in the code above we are calling
obj.getClass().getClassLoader()
.obj
being the instance of the test class that is annotated with@ExtendWith()
.getClass()
is how we get access to theClass
object. If you are unfamiliar with the Class object, you can think of it as an interface between information about our compiled class and our running program. Technically speaking, when a.class
file is loaded into the JVM the JVM creates a Class object and stores it on the heap. Now that class object has references back to an internal data structure called themethod area
. Thismethod area
stores information about or compiled objects. Things like, methods, fields, constructors, interfaces and so on. If this sounds familiar it is because you have used the Java reflections API. The Java reflections API relies very heavily on the ability to access information stored in the method area. So when we callgetClassLoader()
we are accessing themethod area
to find out what class loader was used to load the obj class. There is a possibility that the class loader will be null, in which case we call:
if(classLoader == null){
classLoader = ClassLoader.getSystemClassLoader();
}
- If
obj.getClassLoader()
is null, that simply means that it was loaded by thePrimordial classLoader
(ClassLoader used to boot up the JVM). So in response we get the Primordial classloader by callingClassLoader.getSystemClassLoader()
Dynamic dependency injection
- Next we need to create a new method that is going to dynamically inject anything that is annotated with
@Gucci
and we do so like this:
public Object dynamicInjection(ClassLoader classLoader, String binaryName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
return classLoader.loadClass(binaryName).getDeclaredConstructor().newInstance();
}
the
classLoader.loadClass(binaryName)
is very important to understand. Notice how we are using the same class loader as obj. I am sure you are wondering, why? does it have to be? can we use a different class loader? Well, as it turns out different class loaders have differentname spaces
and if two separate class loaders load the same class, we will get two different versions of the same class that can not talk to each other. So yes it needs to be the same class loader. The binary name is actually just the package followed by a.
and the name of the class. The binary name is used to find the class amongst all of the other classes.getDeclaredConstructor().newInstance()
is how we create a new instance of the field we found.Now we can implement the code that is going to find the annotated fields and assign the new values:
Field [] annotatedField = o.getClass().getDeclaredFields();
for (Field field : annotatedField) {
if (field.isAnnotationPresent(Gucci.class)) {
field.setAccessible(true);
try{
String binaryName = field.getGenericType().getTypeName();
field.set(o,dynamicInjection(classLoader,binaryName) );
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
};
}
}
- The code is pretty self explanatory but here's a quick run down.
o.getClass().getDeclaredFields()
gets us an array of all the fields. We then have aenhanced for loop
that loops over the array. Check if the field is annotatedfield.isAnnotationPresent(Gucci.class)
. If it is then we set its modifier to true withfield.setAccessible(true);
. If we don't do this and the field is private we will get an error. Then we get the binary name of the field,field.getGenericType().getTypeName();
, remember its just the package and the class name. Lastly we set the value of the field,field.set(o,dynamicInjection(classLoader,binaryName)
. With that we have now created a dependency injection annotation with the Junit 5 extension model. Now I am sure you can see the tower of exceptions, which is obviously not ideal. I'm not sure how I want to handle all the exceptions yet. However, I will be digging around the Mockito code base to see if I can find how they handle all their exceptions.
Moving forward
- While this is not yet a mocking library, in the next tutorial I demonstrating on how we can turn this code into an open source maven dependency library. Which we can then further develop into a tiny mocking library.
Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on Twitter.
Top comments (0)