Skip to content

Commit

Permalink
feat: Add JUnit5 extension for OpenFeature (#888)
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Schrottner <simon.schrottner@dynatrace.com>
  • Loading branch information
aepfli authored Jul 26, 2024
1 parent 353f77b commit 9fff9db
Show file tree
Hide file tree
Showing 18 changed files with 1,379 additions and 2 deletions.
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

0 comments on commit 9fff9db

Please sign in to comment.