Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

With JUnit 5, how to share information in `ExtensionContext.Store` between test instances?

Tags:

java

junit

junit5

I am running into trouble with JUnit 5 (5.0 or 5.1) and custom extension.

We are using service loader to load all implementations which then modify how our extension is bootstrapped. These implementations can be loaded just once, so I was thinking of using ExtensionContext.Store and placing it there. Every subsequent test instance would then just load it from Store instead of via service loader.

Now, I am even aware of the hierarchical context structure and I know that there is some "root" context which you can get through ExtensionContext.getRoot(). But this "root" context (instance of JupiterEngineExtensionContext) isn't really root - there is different one for every test instance.

Say you have FooTest and BarTest, then printing out getRoot() for each of them yields:

org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext@1f9e9475 org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext@6c3708b3

And hence trying to retrieve previously stored information from Store fails.

  • Is having this limitation intended? It makes the borderline between ClassExtensionContext and JupiterEngineExtensionContext pretty blurred.
  • Is there another way to globally store some information via extension?

Here is a (very) simplified version of how I tried working with the store (cutting out all other information basically). I also added some System.out.print() calls to underline what I am seeing. Executing this extension on two test classes results in what I described above:

    public class MyExtension implements BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        System.out.println(context.getRoot());
        if (context.getRoot().getStore(Namespace.create(MyExtension.class)).get("someIdentifier", String.class) == null) {
            context.getRoot().getStore(Namespace.create(MyExtension.class)).put("someIdentifier", "SomeFooString");
        } else {
            // this is never executed
            System.out.println("Found it, no need to store anything again!");
        }
    }
}

EDIT: Here is a minimal project on GH(link), run by mvn clean install, which displays the behaviour I see.

like image 604
Siliarus Avatar asked Mar 28 '18 11:03

Siliarus


1 Answers

I just copied your MyExtension verbatim (i.e., with zero changes) and ran both FooTest and BarTest.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MyExtension.class)
class FooTest {

    @Test
    void test() {
    }
}

and

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MyExtension.class)
class BarTest {

    @Test
    void test() {
    }
}

And the result is:

org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext@2280cdac
org.junit.jupiter.engine.descriptor.JupiterEngineExtensionContext@2280cdac
Found it, no need to store anything again!

Thus, getRoot() works as documented.

The only explanation for why you see two different roots is that you must be executing the tests in different processes.

Please keep in mind that the root ExtensionContext instance is bound to the current execution of your test suite.

So if you run FooTest and BarTest one after the other in an IDE, that will actually result in two "test suites" with different roots. The same is true if you configure your build tool to fork between test classes.

Whereas, if you execute both test classes together in a single "test suite" (e.g., by telling your IDE to run all tests in the same package or same source tree) you will then see that there is one root like in the output I provided above.

Note, however, that there was an issue with the junit-platform-surefire-provider prior to version 1.0.3, whereby the provider launched the JUnit Platform for each test class. This would give the appearance of forking even though Surefire did not actually start a new JVM process. For details, see https://github.com/junit-team/junit5/pull/1137.

like image 137
Sam Brannen Avatar answered Oct 28 '22 17:10

Sam Brannen