Junit5 extensions

Dekorate provides two junit5 extensions for:

  • Kubernetes
  • OpenShift
  • Knative

These extensions are dekorate aware and can read generated resources and configuration, in order to manage end to end tests for the annotated applications.

Features

  • Environment conditions
  • Container builds
  • Apply generated manifests to test environment
  • Inject test with:
    • client
    • application pod

Kubernetes extension for JUnit5

The kubernetes extension can be used by adding the following dependency:

<dependency>
  <groupId>io.dekorate</groupId>
  <artifactId>kubernetes-junit</artifactId>
  <version>4.1.3</version>
</dependency>

This dependency gives access to @KubernetesIntegrationTest which is what enables the extension for your tests.

By adding the annotation to your test class the following things will happen:

  1. The extension will check if a kubernetes cluster is available (if not tests will be skipped).
  2. If @DockerBuild is present in the project, a docker build will be triggered.
  3. All generated manifests will be applied.
  4. Will wait until applied resources are ready.
  5. Dependencies will be injected (e.g. KubernetesClient, Pod etc)
  6. Test will run
  7. Applied resources will be removed.
Dependency injection

Supported items for injection:

  • KubernetesClient
  • Pod (the application pod)
  • KubernetesList (the list with all generated resources)

To inject one of this you need a field in the code annotated with @Inject.

For example:

@Inject
KubernetesClient client;

When injecting a Pod, it’s likely we need to specify the pod name. Since the pod name is not known in advance, we can use the deployment name instead. If the deployment is named hello-world then you can do something like:

@Inject
@Named("hello-world")
Pod pod;

Note: It is highly recommended to also add maven-failsafe-plugin configuration so that integration tests only run in the integration-test phase. This is important since in the test phase the application is not packaged. Here’s an example of how it you can configure the project:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${version.maven-failsafe-plugin}</version>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <phase>integration-test</phase>
      <configuration>
        <includes>
          <include>**/*IT.class</include>
        </includes>
      </configuration>
    </execution>
  </executions>
</plugin>
Integration Test Configuration

We can customize the deployment configuration within the test execution using the Kubernetes integration test annotation:

@KubernetesIntegrationTest(deployEnabled = true,
  buildEnabled = true,
  readinessTimeout = 300000,
  additionalModules = {} 
)
class KubernetesIT {
    // ...
}
Field Type Description Overridable using System Property Default Value
deployEnabled boolean Flag to define whether the extension should automatically apply resources. dekorate.test.deploy.enabled true
buildEnabled boolean Flag to define whether the extension should automatically perform container builds. dekorate.test.build.enabled true
readinessTimeout long The amount of time in milliseconds to wait for application to become ready. dekorate.test.readiness.timeout 300000
additionalModules String[] List of additional modules to be loaded by the test framework. dekorate.test.additional-modules  

OpenShift extension for JUnit5

Similarly, to using the kubernetes junit extension you can use the extension for OpenShift, by adding @OpenshiftIntegrationTest. To use that you need to add:

<dependency>
  <groupId>io.dekorate</groupId>
  <artifactId>openshift-junit</artifactId>
  <version>4.1.3</version>
</dependency>

By adding the annotation to your test class the following things will happen:

  1. The extension will check if a kubernetes cluster is available (if not tests will be skipped).
  2. A docker build will be triggered.
  3. All generated manifests will be applied.
  4. Will wait until applied resources are ready.
  5. Dependencies will be injected (e.g. KubernetesClient, Pod etc)
  6. Test will run
  7. Applied resources will be removed.
Integration Test Configuration

We can customize the deployment configuration within the test execution using the OpenShift integration test annotation:

@OpenshiftIntegrationTest(deployEnabled = true,
  buildEnabled = true,
  pushEnabled = false,
  imageStreamTagTimeout = 120000,
  readinessTimeout = 300000,
  additionalModules = {} 
)
class OpenShiftIT {
    // ...
}
Field Type Description Overridable using System Property Default Value
deployEnabled boolean Flag to define whether the extension should automatically apply resources. dekorate.test.deploy.enabled true
buildEnabled boolean Flag to define whether the extension should automatically perform container builds. dekorate.test.build.enabled true
pushEnabled boolean Flag to define whether the extension should automatically push image. dekorate.test.openshift.push.enabled false
imageStreamTagTimeout long The amount of time in seconds to wait for the image stream tags to be available. dekorate.test.openshift.image-stream.timeout 120000
readinessTimeout long The amount of time in milliseconds to wait for application to become ready. dekorate.test.readiness.timeout 300000
additionalModules String[] List of additional modules to be loaded by the test framework. dekorate.test.additional-modules  

Knative extension for JUnit5

The knative extension can be used by adding the following dependency:

<dependency>
  <groupId>io.dekorate</groupId>
  <artifactId>knative-junit</artifactId>
  <version>4.1.3</version>
</dependency>

This dependency gives access to @KnativeIntegrationTest which is what enables the extension for your tests.

By adding the annotation to your test class the following things will happen:

  1. The extension will check if a kubernetes cluster is available (if not tests will be skipped).
  2. If @DockerBuild is present in the project, a docker build will be triggered.
  3. All generated manifests will be applied.
  4. Will wait until applied resources and the Knative services are ready.
  5. Dependencies will be injected (e.g. KnativeClient, Service, Knative Routes, etc)
  6. Test will run
  7. Applied resources will be removed.
Dependency injection

In addition to the supported items from the Kubernetes JUnit 5 extension, the following resources can be injected:

  • KnativeClient
  • Knative Service
  • Knative Route (or the URL of the Knative route)

To inject one of this you need a field in the code annotated with @Inject.

For example:

@Inject
KnativeClient client;

When injecting the URL of a Route, it’s likely we need to specify the route name. For example, if the deployment is named hello-world then you can do something like:

@Inject
@Named("hello-world")
URL knativeAppRouteUrl;

Note: It is highly recommended to also add maven-failsafe-plugin configuration so that integration tests only run in the integration-test phase. This is important since in the test phase the application is not packaged. Here’s an example of how it you can configure the project:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>${version.maven-failsafe-plugin}</version>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <phase>integration-test</phase>
      <configuration>
        <includes>
          <include>**/*IT.class</include>
        </includes>
      </configuration>
    </execution>
  </executions>
</plugin>
Integration Test Configuration

We can customize the deployment configuration within the test execution using the Knative integration test annotation:

@KnativeIntegrationTest(deployEnabled = true,
  buildEnabled = true,
  readinessTimeout = 300000,
  additionalModules = {} 
)
class KnativeIT {
    // ...
}
Field Type Description Overridable using System Property Default Value
deployEnabled boolean Flag to define whether the extension should automatically apply resources. dekorate.test.deploy.enabled true
buildEnabled boolean Flag to define whether the extension should automatically perform container builds. dekorate.test.build.enabled true
readinessTimeout long The amount of time in milliseconds to wait for application to become ready. dekorate.test.readiness.timeout 300000
additionalModules String[] List of additional modules to be loaded by the test framework. dekorate.test.additional-modules  

Configuration externalization

It is often desired to externalize configuration in configuration files, instead of hard coding things inside annotations.

Dekorate provides the ability to externalize configuration to configuration files (properties or yml). This can be done to either override the configuration values provided by annotations, or to use dekorate without annotations.

For supported frameworks, this is done out of the box, as long as the corresponding framework jar is present. The frameworks supporting this feature are:

  • spring boot
  • thorntail

For these frameworks, the use of annotations is optional, as everything may be configured via configuration files. Each annotation may be expressed using properties or yaml using the following steps.

  • Each annotation property is expressed using a key/value pair.
  • All keys start with the dekorate.<annotation kind>. prefix, where annotation kind is the annotation class name in lowercase, stripped of the Application suffix.
  • The remaining part of key is the annotation property name.
  • For nesting properties the key is also nested following the previous rule.

For all other frameworks or generic java application this can be done with the use of the @Dekorate annotation. The presence of this annotation will trigger the dekorate processes. Dekorate will then look for application.properites or application.yml resources. If present, they will be loaded. If not the default configuration will be used.

Examples:

The following annotation configuration:

@KubernetesApplication(labels=@Label(key="foo", value="bar"))
public class Main {
}

Can be expressed using properties:

dekorate.kubernetes.labels[0].key=foo
dekorate.kubernetes.labels[0].value=bar

or using yaml:

dekorate:
  kubernetes:
    labels:
      - key: foo
        value: bar

In the examples above, dekorate is the prefix that we use to namespace the dekorate configuration. kubernetes defines the annotation kind (its @KubernetesApplication in lower case and stripped of the Application suffix). labels, key and value are the property names and since the Label is nested under @KubernetesApplication so are the properties.

The exact same example for OpenShift (where @OpenshiftApplication is used instead) would be:

@OpenshiftApplication(labels=@Label(key="foo", value="bar"))
public class Main {
}

Can be expressed using properties:

dekorate.openshift.labels[0].key=foo
dekorate.openshift.labels[0].value=bar

or using yaml:

dekorate:
  openshift:
    labels:
      - key: foo
        value: bar
Spring Boot

For spring boot, dekorate will look for configuration under:

  • application.properties
  • application.yml
  • application.yaml

Also, it will look for the same files under the kubernetes profile:

  • application-kubernetes.properties
  • application-kubernetes.yml
  • application-kubernetes.yaml
Vert.x & generic Java

For generic java, if the @Dekorate annotation is present, then dekorate will look for confiugration under:

  • application.properties
  • application.yml

These files can be overridden using the configFiles property on the @Dekorate annotation.

For example:

A generic java application annotated with @Dekorate:


    import io.dekorate.annotation.Dekorate;
    
    @Dekorate
    public class Main {
        //do stuff
    }

During compilation kubernetes, OpenShift or both resources will be generated (depending on what dekorate jars are present in the classpath). These resources can be customized using properties:

dekorate.openshift.labels[0].key=foo
dekorate.openshift.labels[0].value=bar

or using yaml:

dekorate:
  openshift:
    labels:
      - key: foo
        value: bar

Testing Multi-Module projects

The Dekorate testing framework supports multi-module projects either using the OpenShift JUnit 5 extension or using the Kubernetes JUnit 5 extension.

A multi-module project consist of multiple modules, all using Dekorate to generate the cluster manifests and a tests module that will run the integration tests:

multi-module-parent
└───module-1
└───module-2
└───tests

In the tests module, we can now specify the location of the additional modules via the field additionalModules which is part of the @OpenshiftIntegrationTest and @KubernetesIntegrationTest annotations:

@OpenshiftIntegrationTest(additionalModules = { "../module-1", "../module-2" })
class SpringBootForMultipleAppsOnOpenshiftIT {

  @Inject
  private KubernetesClient client;

  @Inject
  @Named("module-1")
  Pod podForModuleOne;

  @Inject
  @Named("module-2")
  Pod podForModuleTwo;

  // ...
}

Doing so, the test framework will locate the Dekorate manifests that have been previously generated to build and deploy the application for each integration test.