Skip to content

feat: Add JUnit5 extension for OpenFeature #888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 26, 2024
2 changes: 2 additions & 0 deletions .github/component_owners.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Keep all in alphabetical order
components:
tools/junit-openfeature:
- aepfli
hooks/open-telemetry:
- beeme1mr
- Kavindu-Dodan
Expand Down
5 changes: 3 additions & 2 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"providers/unleash": "0.0.4-alpha",
"providers/flipt": "0.0.2",
"providers/configcat": "0.0.3",
"providers/statsig": "0.0.4"
}
"providers/statsig": "0.0.4",
"tools/junit-openfeature": "0.0.0"
}
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

<modules>
<module>hooks/open-telemetry</module>
<module>tools/junit-openfeature</module>
<module>providers/flagd</module>
<module>providers/flagsmith</module>
<module>providers/go-feature-flag</module>
Expand Down
11 changes: 11 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@
"pom.xml",
"README.md"
]
},
"tools/junit-openfeature": {
"package-name": "dev.openfeature.contrib.tools.junitopenfeature",
"release-type": "simple",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": true,
"versioning": "default",
"extra-files": [
"pom.xml",
"README.md"
]
}
},
"changelog-sections": [
Expand Down
141 changes: 141 additions & 0 deletions tools/junit-openfeature/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# JUnit Open Feature extension

A JUnit5 extension to reduce boilerplate code for testing code which utilizes OpenFeature.

## Getting Started

We are supporting two different flavors for testing, a [simple](#simple-configuration) and an [extended](#extended-configuration) configuration.

Notice: We are most likely not multithread compatible!
### Simple Configuration

Choose the simple configuration if you are only testing in one domain.
Per default, it will be used in the global domain.

```java
@Test
@Flag(name = "BOOLEAN_FLAG", value = "true")
void test() {
// your test code
}
```

#### Multiple flags

The `@Flag` annotation can be also repeated multiple times.

```java
@Test
@Flag(name = "BOOLEAN_FLAG", value = "true")
@Flag(name = "BOOLEAN_FLAG2", value = "true")
void test() {
// your test code
}
```

#### Defining Flags for a whole test-class

`@Flags` can be defined on the class-level too, but method-level
annotations will supersede class-level annotations.

```java
@Flag(name = "BOOLEAN_FLAG", value = "true")
@Flag(name = "BOOLEAN_FLAG2", value = "false")
class Test {
@Test
@Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used
void test() {
// your test code
}
}
```

#### Setting a different domain

You can define your own domain on the test-class-level with `@OpenFeatureDefaultDomain` like:

```java
@OpenFeatureDefaultDomain("domain")
class Test {
@Test
@Flag(name = "BOOLEAN_FLAG", value = "true")
// this flag will be available in the `domain` domain
void test() {
// your test code
}
}
```

### Extended Configuration

Use the extended configuration when your code needs to use multiple domains.

```java
@Test
@OpenFeature({
@Flag(name = "BOOLEAN_FLAG", value = "true")
})
@OpenFeature(
domain = "domain",
value = {
@Flag(name = "BOOLEAN_FLAG2", value = "true")
})
void test() {
// your test code
}
```


#### Multiple flags

The `@Flag` annotation can be also repeated multiple times.

```java
@Test
@OpenFeature({
@Flag(name = "BOOLEAN_FLAG", value = "true"),
@Flag(name = "BOOLEAN_FLAG2", value = "true")
})
void test() {
// your test code
}
```

#### Defining Flags for a whole test-class

`@Flag` can be defined on the class-level too, but method-level
annotations will superseded class-level annotations.

```java
@OpenFeature({
@Flag(name = "BOOLEAN_FLAG", value = "true"),
@Flag(name = "BOOLEAN_FLAG2", value = "false")
})
class Test {
@Test
@OpenFeature({
@Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used
})
void test() {
// your test code
}
}
```

#### Setting a different domain

You can define an own domain for each usage of the `@OpenFeature` annotation with the `domain` property:

```java
@Test
@OpenFeature(
domain = "domain",
value = {
@Flag(name = "BOOLEAN_FLAG2", value = "true") // will be used
})
// this flag will be available in the `domain` domain
void test() {
// your test code
}
```

49 changes: 49 additions & 0 deletions tools/junit-openfeature/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.openfeature.contrib</groupId>
<artifactId>parent</artifactId>
<version>0.1.0</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<groupId>dev.openfeature.contrib.tools</groupId>
<artifactId>junitopenfeature</artifactId>
<version>0.0.0</version> <!--x-release-please-version -->

<name>junit-openfeature-extension</name>
<description>JUnit5 Extension for OpenFeature</description>
<url>https://openfeature.dev</url>

<developers>
<developer>
<id>aepfli</id>
<name>Simon Schrottner</name>
<organization>OpenFeature</organization>
<url>https://openfeature.dev/</url>
</developer>
</developers>

<dependencies>
<dependency>
<groupId>dev.openfeature</groupId>
<artifactId>sdk</artifactId>
<version>[1.4,2.0)</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.jupiter.version}</version>
</dependency>

<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>1.9.1</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.openfeature.contrib.tools.junitopenfeature;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for Flag Configuration for the default domain.
* Can be used as a standalone flag configuration but also within {@link OpenFeature}.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Flags.class)
@ExtendWith(OpenFeatureExtension.class)
public @interface Flag {
/**
* The key of the FeatureFlag.
*/
String name();

/**
* The value of the FeatureFlag.
*/
String value();

/**
* The type of the FeatureFlag.
*/
Class<?> valueType() default Boolean.class;
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.openfeature.contrib.tools.junitopenfeature;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Collection of {@link Flag} configurations.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(OpenFeatureExtension.class)
public @interface Flags {
/**
* Collection of {@link Flag} configurations.
*/
Flag[] value() default {};
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package dev.openfeature.contrib.tools.junitopenfeature;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation for generating an extended configuration for OpenFeature.
* This annotation allows you to specify a list of flags for a specific domain.
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = OpenFeatures.class)
@ExtendWith(OpenFeatureExtension.class)
public @interface OpenFeature {
/**
* the provider domain used for this configuration.
*/
String domain() default "";
/**
* Collection of {@link Flag} configurations for this domain.
*/
Flag[] value();
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.openfeature.contrib.tools.junitopenfeature;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Configuration of a default domain for standalone {@link Flag} configurations.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(OpenFeatureExtension.class)
public @interface OpenFeatureDefaultDomain {
/**
* the default domain.
*/
String value() default "";
}


Loading