Search code examples
javaspring-bootjunit4program-entry-pointload-time-weaving

How to dynamically instrument JUnit tests?


I am using Invesdwin (https://github.com/subes/invesdwin-instrument) to dynamically load java agents into my Spring Boot project in the main method, before the context starts :

DynamicInstrumentationLoader.waitForInitialized();
DynamicInstrumentationLoader.initLoadTimeWeavingContext();

ApplicationContext springContext = SpringApplication.run(Some_Service.class);
...

This works great because it eliminates the need for adding -javaagent parameters when running the java -jar command from the command line.

The issue arises when it comes to unit tests. Since they don't have a main method (that I can tap into that is), I cannot make those 2 lines run before the Spring Context initializes. Without those arguments, each test will cause the context to fail to load with this error :

ClassLoader [jdk.internal.loader.ClassLoaders$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-instrument-{version}.jar

I can work around this during the final build by setting up the Surefire plugin this way in my POM :

<!--Maven Surefire Plugin-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>test</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <argLine>
                    -javaagent:lib/aspectjweaver-1.9.5.jar 
                    -javaagent:lib/spring-instrument-5.2.3.RELEASE.jar
                </argLine>
            </configuration>
          </plugin>

Unfortunately this only works during the final build phase. Running an individual test method in Eclipse will fail unless I manually add those arguments into the run configuration for that test which is a pain to say the least.

I made a custom runner class in an attempt to make code run before the Spring context initializes, like so :

public class WeavingRunner extends SpringJUnit4ClassRunner
{
    public WeavingRunner(Class<?> clazz) throws InitializationError 
    {
        super(clazz);

        DynamicInstrumentationLoader.waitForInitialized();
        DynamicInstrumentationLoader.initLoadTimeWeavingContext();
    }
}

Although the Eclipse console does give me hints that the weaving is happening when my base test uses this runner class instead of SpringRunner, I get all sorts of weaving errors that seem to indicate the dynamic weaving hasn't happened soon enough :

java.lang.NoSuchMethodException: com.something.SomeAspectClass.aspectOf()

Is there a known way to replicate code running in a main method when running JUnit tests?

******EDIT******

I noticed something extremely weird with this. If I run the package enclosing the tests as a JUnit test, it works! The above weaving errors only appear when running the src/test/java folder as a JUnit test in Eclipse or running the app itself as a JUnit test (which I ultimately need). The dynamic weaving is working but somehow something is making it only work when running individual tests or up to the enclosing package as a JUnit test. I hope that makes sense!

I was starting to suspect issues with my aop.xml file but how could that be the issue if running individual tests and even entire packages works fine?!


Solution

  • JUnit might do classpath scanning to discover unit tests. Also junit classes with all their dependencies will be loaded before invesdwin-instrument can be invoked inside the test. Thus the unit test class itself can not use aspects. The only workaround I know for this is to put the aspect usage into a nested class which gets loaded after the test class is initialized and thus invesdwin-instrument is loaded. An example for this pattern where @Transactional aspects are tested is here: https://github.com/invesdwin/invesdwin-context-persistence/blob/master/invesdwin-context-persistence-parent/invesdwin-context-persistence-jpa-hibernate/src/test/java/de/invesdwin/context/persistence/jpa/hibernate/MultiplePersistenceUnitsTest.java