DEV Community

Cover image for Reverse engineering Mockito. Part 2. Dynamic dependency injection
Tristan Elliott
Tristan Elliott

Posted on • Edited on

Reverse engineering Mockito. Part 2. Dynamic dependency injection

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

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 the postProcessTestInstance 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();
        }

Enter fullscreen mode Exit fullscreen mode
  • 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 the Class 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 the method area. This method 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 call getClassLoader() we are accessing the method 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();
        }
Enter fullscreen mode Exit fullscreen mode
  • If obj.getClassLoader() is null, that simply means that it was loaded by the Primordial classLoader (ClassLoader used to boot up the JVM). So in response we get the Primordial classloader by calling ClassLoader.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();
    }

Enter fullscreen mode Exit fullscreen mode
  • 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 different name 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();
                }

            };
        }

    }

Enter fullscreen mode Exit fullscreen mode
  • 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 a enhanced for loop that loops over the array. Check if the field is annotated field.isAnnotationPresent(Gucci.class). If it is then we set its modifier to true with field.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)