Testing
This extension provides a JUnit 5 extension for overriding feature flags in tests. Add the following dependency:
<dependency>
<groupId>io.quarkiverse.openfeature</groupId>
<artifactId>quarkus-openfeature-junit</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
Use the @TestFlag annotation on test methods or test classes to override flag values for the duration of a test:
import dev.openfeature.sdk.FlagValueType;
import io.quarkiverse.openfeature.junit.TestFlag;
@QuarkusTest
public class MyFeatureTest {
@Inject
Client client;
@Test
@TestFlag(key = "new-greeting", value = "true")
void newGreetingEnabled() {
// "new-greeting" evaluates to true for this test
assertThat(client.getBooleanValue("new-greeting", false)).isTrue();
}
@Test
void newGreetingDefault() {
// no @TestFlag here -- the real provider value is used
// ...
}
@Test
@TestFlag(key = "ui-variant", value = "dark-mode", type = FlagValueType.STRING)
@TestFlag(key = "max-retries", value = "5", type = FlagValueType.INTEGER)
void multipleFlags() {
assertThat(client.getStringValue("ui-variant", "default")).isEqualTo("dark-mode");
assertThat(client.getIntegerValue("max-retries", 3)).isEqualTo(5);
}
}
To override flag values by hand in a running application, instead of in a test, use the Dev UI.
Supported test modes
@TestFlag only works under @QuarkusTest.
The overrides are pushed directly into the provider instances of the running application, which requires the test and the application to live in the same classloader.
That is not the case for:
@QuarkusIntegrationTest-
the application runs in a separate process (or container, or as a native executable), so there are no provider instances to push anything to.
QuarkusUnitTest-
the application runs in the same JVM, but the JUnit engine instantiates the extension outside the Quarkus classloader, so it does not see the application’s providers.
In both cases, a test that declares @TestFlag fails with an IllegalStateException saying that no provider exists for the domain.
The same error is raised when the domain attribute names a domain that has no providers, which is usually a typo.
Test classes that declare no @TestFlag at all are unaffected and run normally in every mode.
Value types
The @TestFlag annotation supports the following value types (via the type attribute):
-
BOOLEAN(default) — must be"true"or"false"(case-insensitive) -
STRING— used as-is -
INTEGER— parsed viaInteger.parseInt() -
LONG— parsed viaLong.parseLong() -
DOUBLE— parsed viaDouble.parseDouble()
A value that cannot be parsed as the declared type fails the test, so a typo such as value = "ture" is an error rather than a flag quietly overridden to false.
Note that OBJECT is not supported and will result in an exception.
Fall-through to real providers
Flags that are not overridden fall through to the real provider, so you can mix overrides with real flag values in the same test.
Class-level and method-level annotations
When @TestFlag is placed on a class, the override applies to all test methods in that class.
Method-level @TestFlag annotations take precedence over class-level annotations for the same flag key.
Overrides are always cleared after each test method, so subsequent tests see the real provider values.
Named domains
For named domains, use the domain attribute:
@Test
@TestFlag(domain = "experimentation", key = "experiment-a", value = "true")
void experimentEnabled() {
// ...
}
Provider support
Testing support requires the provider to expose an additional contract. All providers included in this extension do this and so support testing out of the box.
Parallel test execution
@TestFlag is not safe under parallel test execution.
Overrides are applied to the provider itself, which is a singleton shared by the whole application, so they are visible to every test running at the same time.
Two tests that override the same flag with different values, or one test that overrides a flag while another expects the real provider value, will interfere with each other, and the failures depend on timing.
Do not enable JUnit parallel execution (junit.jupiter.execution.parallel.enabled) for test classes that use @TestFlag.
If you need parallelism elsewhere in the test suite, keep the flag-overriding tests in classes that run sequentially, for example with @Execution(ExecutionMode.SAME_THREAD).
Comparison with the upstream OpenFeature JUnit extension
The upstream OpenFeature JUnit extension (dev.openfeature.contrib.tools:junitopenfeature) provides similar annotation-driven flag overrides for tests.
While both extensions cover the same use case, their approach differs significantly.
- Global state
-
This extension pushes test overrides into each provider before a test and clears them after, leaving the provider in its original state. The upstream extension replaces the registered provider for each domain with a test-only provider. This mutates global state and is not reversed after the test, which can cause interference between tests.
- Fall-through to real providers
-
This extension injects test overrides into existing providers. Flags that are not overridden continue to be evaluated by the real provider, so you can test a specific scenario while relying on real flag values for everything else. The upstream extension replaces the provider entirely with a test-only in-memory provider. Any flag not declared in the test annotations returns the default value, not the value from your actual provider.
- Dependencies
-
This extension has no additional dependencies beyond JUnit Jupiter. The upstream extension pulls in JUnit Pioneer and
commons-lang3as transitive dependencies.
In a Quarkus application, use quarkus-openfeature-junit.
The upstream extension is designed for plain Java projects that use the OpenFeature SDK without a framework.