LaunchDarkly Java Testing and Local Development Hints

Last Updated:  April 28, 2022 | Published: April 28, 2022

Feature flags offer a solution to decouple the deployment of a feature from its release. This can help us continuously deploy new changes without releasing features immediately to the public. LaunchDarkly is one of the major players in the feature flag ecosystem and provides SDKs for almost every use case and programming language. This article will cover local development and testing hints for using LaunchDarkly for a Java project.

This article assumes you have a basic understanding of LaunchDarkly and the concept of feature flags. It's not a beginner-friendly introduction to feature toggling or LanchDarkly. Consult the LaunchDarkly documentation to get started.

LaunchDarkly Java Maven Project Setup

Integrating LaunchDarkly to a Java project takes two things: a valid LaunchDarkly access key and a new dependency for our project:

To make things simple, let's assume we're using the following abstraction for our LaunchDarkly feature flag setup:

The functionality is minimalistic and only allows to query for the string value of a feature flag. We're using an interface to abstract any LaunchDarkly internals from the users of our feature flag mechanism.

We provide the actual LaunchDarkly feature flag client by implementing the FeatureFlagClient interface:

This way we hide any LaunchDarkly internals and can potentially switch the underlying feature flag implementation without much effort.

For demonstration purposes, let's stay framework-independent and introduce a good old Java factory that instantiates an actual FeatureFlagClient:

On production, we require an online LDClient that talks to the LaunchDarkly API.  Therefore we pass the secret access key.

When using frameworks like Spring, Quarkus, Micronaut, or Jakarta EE, we'd rather go for a dependency injection approach and let the container create a singleton instance for our FeatureFlagClient.

For local development and testing purposes, the requirements for the LDClient are quite different. We may not want any real HTTP communication going on between our application and the LaunchDarkly servers.

Therefore, let's see what alternatives we have.

Local Java Development and LaunchDarkly

When starting our application locally a real LDClient may not be the best option.

First, we would have to share the SDK key in a secure manner. That shouldn't be a big problem if we introduce a dedicated local environment on LaunchDarkly, as the access key would differ from production.

However, there's still a potential downside as our team members develop features in parallel. Hence, two team members may override their feature flag state constantly.

Furthermore, connecting to LaunchDarkly locally results in additional API calls. If we're short on our API budget, this is another reason against an online version of the LDClient for local development.

Still, we want our developers to be able to test their feature toggled implementations locally.

As a solution, we can create a version of the LDClient that is backed by a local file. This file includes the state of all feature flags and can be changed while the application is running. This way, there's no need to interact with the LaunchDarkly web interface, as we can toggle features by simply modifying a local file.

We enrich our FeatureFlagClientFactory with a new method that creates such an offline file-based LDClient:

The access key is ignored as there won't be any communication with the LaunchDarkly API. We pass the location of the file(s) (which can be multiple) using the  dataSource method. With autoUpdate(true) we instruct the client to listen to file changes and adjust the feature flag values accordingly.

We can choose between a simple (without targeting rules, the values apply to everyone) and a complex (with targeting rules) JSON/YAML file for the local feature flag file.

For demonstration purposes, let's take the simple route and store the following file at the root of our project:

What's left is to adjust our component setup/bean wiring to use this version of the FeatureFlagClient when working locally.

Every developer can now test their changes in an isolated environment and toggle on or off features on demand.

Local LaunchDarkly Development Alternatives

As an alternative to this file-based approach, we can create an offline client that always returns the default value:

We can use this as a backup if all other approaches don't fit our use case.

The benefit of these two LDClient variants is that they don't require any active network connection. No calls are made over the network. This allows us even to develop our application on planes or a German train.

LaunchDarkly Use Case: Change the Root Log Level

Let's implement a feature flag use case to transition to our next topic: testing with LaunchDarkly.

We're implementing a generic feature flag use case that's valid for many applications: Changing the root log level during runtime. While this is more a configuration and less a feature flag in the sense of experiments and A/B testing, it's still a good usage of LaunchDarkly to get started with feature flags.

When using Log4j2, the required code to change the root log level of our application is little:

Please note that this implementation only changes the root log level. Specific loggers that define the log level for a package or class are untouched. We'll only see log level changes for statements related to the root logger.

Depending on the application framework we're using, there may be built-in mechanisms for this, but this technique comes in handy for older or plain Java SE applications.

We're registering a feature flag change listener for this feature flag evaluation. Our LaunchDarkly client does the following for this under the hood:

Again, this is a really basic implementation for demonstration purposes.

What's left is to create the feature flag root-log-level inside the LaunchDarkly interface, configure the valid values (e.g. TRACE, DEBUG, ERROR), and define a default log level to then activate the feature flag.

With this listener in place, how are we now going to test it?

Java Unit Testing With LaunchDarkly

When it comes to testing our Java code that depends on a feature flag evaluation, we need a similar solution compared to local development. We do not want to initialize a real LaunchDarkly client when running our test suite locally or on our CI server (e.g., GitHub Actions).

Let's assume we want to write a unit test for the following Java class:

Our OrderService depends on the FeatueFlagClient and evaluates the primary-shipment-method feature flag when processing orders. For the corresponding unit test of this method, we want to verify the behavior of our class for at least two cases: the primary shipment method is plane and is not plane.

When instantiating our class under test (OrderService) we don't want to depend on a real implementation of the FeatueFlagClient and hence use Mockito to mock it:

Mocking the feature flag client allows us to control its behavior during the test execution fully. This way, we can make it return any feature flag value we need for a particular test.

One additional benefit of using our FeatureFlagClient abstraction over directly depending on the LDClient is that we don't need the Mockito inline-mock-maker. The LDClient is a final class, and we would need to tune Mockito to mock this class if we would directly depend on it. That's no big deal per se.

See the static mocking or constructor mocking with Mockito for an example of how to activate this mock maker.

Java Integration Testing Setup With LaunchDarkly

Coming back to our root log level change listener, we need a different solution to test this class. It gets quite complicated (if not impossible) to write a test for this class if we solely depend on Mockito and try to fake the behavior of LaunchDarkly.

Furthermore, when writing integration or end-to-end tests, we might want to have more fine-grained control over the current feature flag values. We may even want to change their value during a test to verify a feature flag value listener fires correctly.

We can use a file-based LaunchDarkly client as showcased for the local development section. But there's an even better alternative: Implementing a LaunchDarkly client backed by an in-memory dataset:

As we abstract the underlying feature flag implementation with our FeatureFlagClient interface, we can provide a new implementation of it inside src/test/java. Hence it will be only accessible for our tests, and we can't accidentally use it in production.

We still use the underlying LDClient from LaunchDarkly but use a TestData instance as the data source. The TestData class is part of the LaunchDarkly Java SDK and is an in-memory representation of our feature flag state.

We can easily modify the current feature flag value by mutating the state of the TestData instance. Users of this test feature flag client use the updateFeatureFlag method to change the feature flags at their convenience.

Java LaunchDarkly Integration Test Example

Let's use this new TestDataFeatureFlagClient to test our RootLogLevelUpdater:

As our class under test depends on an instance of the FeatureFlagClient interface, we can simply swap the implementation for testing purposes. We do so by passing an instance of the TestDataFeatureFlagClient class.

Given the fact that we now have full control over the feature flag value and can change it on-demand during the test execution, let's implement a test to verify our log level change:

We first assert that we don't start with a TRACE log level to ensure we actually change the level. Next, we call our class under test and register the feature flag change listener.

What's left is to trigger the listener by changing the root log level to trace. What follows is a verification using Awaitility (part of the Testing Toolbox). As this feature flag listener is an asynchronous operation, we can't immediately assert that the root log level has changed. We give the test two seconds to detect the change.

Summary: LaunchDarkly Java Testing and Development Hints

With this article, we saw various recipes to provide a convenient use of LaunchDarkly for local Java development and testing. The technique we used for testing the feature flag listener can be used to test the application as a whole. When using Spring Boot, for example, we could override the FeatureFlagClient bean for testing purposes and use a bean of type TestDataFeatureFlagClient.

The in-memory TestData approach for writing integration tests could also work for local development. However, using a file-based approach provides a more straightforward way of interacting with the current feature flag state.

Overall, LaunchDarkly is an excellent tool to master feature flags. If you need a solution to get started with feature flags as a concept and don't want to invest some $$$, take a look at the open-source alternative Togglz.

You can find additional content around feature toggling on Tom's reflectoring.io blog:

The source code for this blog post is available on GitHub.

Joyful testing,

Philip

>