|
Is there a way to have a global, shared resource across different I have a use-case where I want to store some data / metadata from all tests in a global singleton for some analysis, etc. My real setup uses the The current setup works perfectly fine, however, we are now starting to experiment with the Quarkus public class GlobalSingleton {
private static final GlobalSingleton INSTANCE = new GlobalSingleton();
private int counter = 0;
private GlobalSingleton() {
System.err.println("CONSTRUCTOR!");
}
public static GlobalSingleton get() {
return INSTANCE;
}
public void doStuff() {
counter++;
System.out.printf("counter=%d -- %s\n", counter, this);
}
}When I run the following tests: @QuarkusTest
@TestProfile(ProfileTest.Profile.class)
public class ProfileTest {
public static class Profile implements QuarkusTestProfile {
}
@BeforeEach
public void setup() {
GlobalSingleton.get().doStuff();
}
@Test
void t1() {
assertNotNull(new Object());
}
@Test
void t2() {
assertNull(null);
}
}
@QuarkusTest
public class RegularTest {
@BeforeEach
public void setup() {
GlobalSingleton.get().doStuff();
}
@Test
void t1() {
assertNotNull(new Object());
}
@Test
void t2() {
assertNull(null);
}
}I expect that only one instance of the It looks like the I am using Quarkus 3.38.1 with Java 25. For anyone interested, I have pushed the full code for the reproducer here: https://github.com/mensinda/quarkus-stuff/tree/quarkusGlobalTestProfile |
Replies: 2 comments 2 replies
|
Hey! This is actually expected behavior from Quarkus — when you switch between @TestProfiles, Quarkus fully restarts the application and creates a brand new The easiest fix I've found is using System.setProperty() / System.getProperty() — these live in the JVM itself (not inside the Quarkus ClassLoader), so they survive public class GlobalSingleton {
private static final String KEY = "myapp.test.counter";
public void doStuff() {
int count = Integer.parseInt(System.getProperty(KEY, "0")) + 1;
System.setProperty(KEY, String.valueOf(count));
System.out.printf("counter=%d (JVM-global)%n", count);
}
}If you need to store more complex structured data across profiles, appending to a temp file is also pretty reliable: Files.writeString(
Path.of(System.getProperty("java.io.tmpdir"), "test-metadata.ndjson"),
objectMapper.writeValueAsString(entry) + "\n",
StandardOpenOption.APPEND, StandardOpenOption.CREATE
);Basically anything in the parent (system) ClassLoader — like java.lang.System or the JUnit runner itself — will survive the restart. Everything in your test/application |
|
There's another option, which is to leverage parent-first classloading (in combination with static fields). You can configure artifacts as parent first (see https://quarkus.io/guides/class-loading-reference#parent-first-dependencies and https://quarkus.io/guides/all-config#quarkus-core_quarkus-class-loading-parent-first-artifacts), but that could be an annoying granularity. You'd have to make a mini-project which just had the static holder, ideally with no non-JVM dependencies. |
Hey! This is actually expected behavior from Quarkus — when you switch between @TestProfiles, Quarkus fully restarts the application and creates a brand new
QuarkusClassLoader. The tricky part is that Java's static final runs once per ClassLoader, not per JVM, so your singleton gets re-created each time. CDI
@ApplicationScoped fails for the same reason.
The easiest fix I've found is using System.setProperty() / System.getProperty() — these live in the JVM itself (not inside the Quarkus ClassLoader), so they survive
restarts: