Skip to content

Commit 9fff9db

Browse files
authored
feat: Add JUnit5 extension for OpenFeature (#888)
Signed-off-by: Simon Schrottner <[email protected]>
1 parent 353f77b commit 9fff9db

18 files changed

+1379
-2
lines changed

.github/component_owners.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Keep all in alphabetical order
22
components:
3+
tools/junit-openfeature:
4+
- aepfli
35
hooks/open-telemetry:
46
- beeme1mr
57
- Kavindu-Dodan

.release-please-manifest.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"providers/unleash": "0.0.4-alpha",
99
"providers/flipt": "0.0.2",
1010
"providers/configcat": "0.0.3",
11-
"providers/statsig": "0.0.4"
12-
}
11+
"providers/statsig": "0.0.4",
12+
"tools/junit-openfeature": "0.0.0"
13+
}

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
<modules>
3030
<module>hooks/open-telemetry</module>
31+
<module>tools/junit-openfeature</module>
3132
<module>providers/flagd</module>
3233
<module>providers/flagsmith</module>
3334
<module>providers/go-feature-flag</module>

release-please-config.json

+11
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@
110110
"pom.xml",
111111
"README.md"
112112
]
113+
},
114+
"tools/junit-openfeature": {
115+
"package-name": "dev.openfeature.contrib.tools.junitopenfeature",
116+
"release-type": "simple",
117+
"bump-minor-pre-major": true,
118+
"bump-patch-for-minor-pre-major": true,
119+
"versioning": "default",
120+
"extra-files": [
121+
"pom.xml",
122+
"README.md"
123+
]
113124
}
114125
},
115126
"changelog-sections": [

tools/junit-openfeature/README.md

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# JUnit Open Feature extension
2+
3+
A JUnit5 extension to reduce boilerplate code for testing code which utilizes OpenFeature.
4+
5+
## Getting Started
6+
7+
We are supporting two different flavors for testing, a [simple](#simple-configuration) and an [extended](#extended-configuration) configuration.
8+
9+
Notice: We are most likely not multithread compatible!
10+
### Simple Configuration
11+
12+
Choose the simple configuration if you are only testing in one domain.
13+
Per default, it will be used in the global domain.
14+
15+
```java
16+
@Test
17+
@Flag(name = "BOOLEAN_FLAG", value = "true")
18+
void test() {
19+
// your test code
20+
}
21+
```
22+
23+
#### Multiple flags
24+
25+
The `@Flag` annotation can be also repeated multiple times.
26+
27+
```java
28+
@Test
29+
@Flag(name = "BOOLEAN_FLAG", value = "true")
30+
@Flag(name = "BOOLEAN_FLAG2", value = "true")
31+
void test() {
32+
// your test code
33+
}
34+
```
35+
36+
#### Defining Flags for a whole test-class
37+
38+
`@Flags` can be defined on the class-level too, but method-level
39+
annotations will supersede class-level annotations.
40+
41+
```java
42+
@Flag(name = "BOOLEAN_FLAG", value = "true")
43+
@Flag(name = "BOOLEAN_FLAG2", value = "false")
44+
class Test {
45+
@Test
46+
@Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used
47+
void test() {
48+
// your test code
49+
}
50+
}
51+
```
52+
53+
#### Setting a different domain
54+
55+
You can define your own domain on the test-class-level with `@OpenFeatureDefaultDomain` like:
56+
57+
```java
58+
@OpenFeatureDefaultDomain("domain")
59+
class Test {
60+
@Test
61+
@Flag(name = "BOOLEAN_FLAG", value = "true")
62+
// this flag will be available in the `domain` domain
63+
void test() {
64+
// your test code
65+
}
66+
}
67+
```
68+
69+
### Extended Configuration
70+
71+
Use the extended configuration when your code needs to use multiple domains.
72+
73+
```java
74+
@Test
75+
@OpenFeature({
76+
@Flag(name = "BOOLEAN_FLAG", value = "true")
77+
})
78+
@OpenFeature(
79+
domain = "domain",
80+
value = {
81+
@Flag(name = "BOOLEAN_FLAG2", value = "true")
82+
})
83+
void test() {
84+
// your test code
85+
}
86+
```
87+
88+
89+
#### Multiple flags
90+
91+
The `@Flag` annotation can be also repeated multiple times.
92+
93+
```java
94+
@Test
95+
@OpenFeature({
96+
@Flag(name = "BOOLEAN_FLAG", value = "true"),
97+
@Flag(name = "BOOLEAN_FLAG2", value = "true")
98+
})
99+
void test() {
100+
// your test code
101+
}
102+
```
103+
104+
#### Defining Flags for a whole test-class
105+
106+
`@Flag` can be defined on the class-level too, but method-level
107+
annotations will superseded class-level annotations.
108+
109+
```java
110+
@OpenFeature({
111+
@Flag(name = "BOOLEAN_FLAG", value = "true"),
112+
@Flag(name = "BOOLEAN_FLAG2", value = "false")
113+
})
114+
class Test {
115+
@Test
116+
@OpenFeature({
117+
@Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used
118+
})
119+
void test() {
120+
// your test code
121+
}
122+
}
123+
```
124+
125+
#### Setting a different domain
126+
127+
You can define an own domain for each usage of the `@OpenFeature` annotation with the `domain` property:
128+
129+
```java
130+
@Test
131+
@OpenFeature(
132+
domain = "domain",
133+
value = {
134+
@Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used
135+
})
136+
// this flag will be available in the `domain` domain
137+
void test() {
138+
// your test code
139+
}
140+
```
141+

tools/junit-openfeature/pom.xml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>dev.openfeature.contrib</groupId>
7+
<artifactId>parent</artifactId>
8+
<version>0.1.0</version>
9+
<relativePath>../../pom.xml</relativePath>
10+
</parent>
11+
<groupId>dev.openfeature.contrib.tools</groupId>
12+
<artifactId>junitopenfeature</artifactId>
13+
<version>0.0.0</version> <!--x-release-please-version -->
14+
15+
<name>junit-openfeature-extension</name>
16+
<description>JUnit5 Extension for OpenFeature</description>
17+
<url>https://openfeature.dev</url>
18+
19+
<developers>
20+
<developer>
21+
<id>aepfli</id>
22+
<name>Simon Schrottner</name>
23+
<organization>OpenFeature</organization>
24+
<url>https://openfeature.dev/</url>
25+
</developer>
26+
</developers>
27+
28+
<dependencies>
29+
<dependency>
30+
<groupId>dev.openfeature</groupId>
31+
<artifactId>sdk</artifactId>
32+
<version>[1.4,2.0)</version>
33+
<scope>provided</scope>
34+
</dependency>
35+
36+
<dependency>
37+
<groupId>org.junit.jupiter</groupId>
38+
<artifactId>junit-jupiter-api</artifactId>
39+
<version>${junit.jupiter.version}</version>
40+
</dependency>
41+
42+
<dependency>
43+
<groupId>org.junit-pioneer</groupId>
44+
<artifactId>junit-pioneer</artifactId>
45+
<version>1.9.1</version>
46+
</dependency>
47+
</dependencies>
48+
49+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package dev.openfeature.contrib.tools.junitopenfeature;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Repeatable;
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.RetentionPolicy;
9+
import java.lang.annotation.Target;
10+
11+
/**
12+
* Annotation for Flag Configuration for the default domain.
13+
* Can be used as a standalone flag configuration but also within {@link OpenFeature}.
14+
*/
15+
@Target({ElementType.METHOD, ElementType.TYPE})
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@Repeatable(Flags.class)
18+
@ExtendWith(OpenFeatureExtension.class)
19+
public @interface Flag {
20+
/**
21+
* The key of the FeatureFlag.
22+
*/
23+
String name();
24+
25+
/**
26+
* The value of the FeatureFlag.
27+
*/
28+
String value();
29+
30+
/**
31+
* The type of the FeatureFlag.
32+
*/
33+
Class<?> valueType() default Boolean.class;
34+
}
35+
36+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.openfeature.contrib.tools.junitopenfeature;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Collection of {@link Flag} configurations.
12+
*/
13+
@Target({ElementType.METHOD, ElementType.TYPE})
14+
@Retention(RetentionPolicy.RUNTIME)
15+
@ExtendWith(OpenFeatureExtension.class)
16+
public @interface Flags {
17+
/**
18+
* Collection of {@link Flag} configurations.
19+
*/
20+
Flag[] value() default {};
21+
}
22+
23+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package dev.openfeature.contrib.tools.junitopenfeature;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Repeatable;
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.RetentionPolicy;
9+
import java.lang.annotation.Target;
10+
11+
/**
12+
* Annotation for generating an extended configuration for OpenFeature.
13+
* This annotation allows you to specify a list of flags for a specific domain.
14+
*/
15+
@Target({ ElementType.METHOD, ElementType.TYPE })
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@Repeatable(value = OpenFeatures.class)
18+
@ExtendWith(OpenFeatureExtension.class)
19+
public @interface OpenFeature {
20+
/**
21+
* the provider domain used for this configuration.
22+
*/
23+
String domain() default "";
24+
/**
25+
* Collection of {@link Flag} configurations for this domain.
26+
*/
27+
Flag[] value();
28+
}
29+
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.openfeature.contrib.tools.junitopenfeature;
2+
3+
import org.junit.jupiter.api.extension.ExtendWith;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
10+
/**
11+
* Configuration of a default domain for standalone {@link Flag} configurations.
12+
*/
13+
@Target({ElementType.TYPE})
14+
@Retention(RetentionPolicy.RUNTIME)
15+
@ExtendWith(OpenFeatureExtension.class)
16+
public @interface OpenFeatureDefaultDomain {
17+
/**
18+
* the default domain.
19+
*/
20+
String value() default "";
21+
}
22+
23+

0 commit comments

Comments
 (0)