Building a Feature Toggle framework in React

Jun 20, 2020

Contributor

Jake Grafenstein avatar

Jake Grafenstein

Author

Full-stack developer passionate about clean code and effective testing strategies.


When your side project starts generating real users, you have to start thinking more intelligently about how to release new features. Before, you could merge and deploy half-baked code because the risks of doing so were low. After all, the handful of people who were using the application were early adopters who understood the application was still in development. Now, your users are expecting your features to work as soon as they get their hands on them.

So how do you deal with this? In the spirit of keeping pull requests small and easily testable, you want to keep merging code incrementally, but you also need to make sure that users can't find and use the features that are still in progress.

A feature toggle framework will allow you to start building new features while protecting your application's brand. By hiding in progress features behind a feature toggle, you can safely release your code without actually releasing the features to end users.

How Feature Toggles Work

In theory, feature toggles are a very simple concept. In practice, they can be quite a challenge to get right. At its core, a feature toggle is a boolean value that you reference in your code to determine the code path that is executed. A simple feature toggle can be defined as a constant in a single file that is initially set to false. Then, you can use the constant as the condition for an if statement to separate the code paths for released and unreleased content.

In practice, you're going to want a more robust framework for feature toggles, especially when you consider the testing burden of having branching logic in your code. Referencing a constant directly will ensure that any tests you write will only take the code path for the released feature. As a good developer, you want to ensure that every piece of code that is released is well tested, even if it isn't visible to the end user.

It's tempting to go crazy when building out a framework for handling this. I would caution against over-engineering as it can be easy to fall down a rabbit hole of complexity. Make sure to K.I.S.S. the problem — Keep It Simple, Stupid!

Below is a simple, yet functional solution to this problem that we are using in the code for Dashboi. Feel free to copy the strategy here to use in your own application!

Step 1: Define your toggle

A feature toggle should be named so that it is easy to understand. For this simple example, we'll build a feature toggle around a new feature that shows a welcome message to the user.

const IS_PERSONALIZED_WELCOME_MESSAGE_ENABLED = false;

Instead of referencing this toggle directly, we'll build a getter method to retrieve it from the functional code. The purpose of this method will become clear once we start testing the code we write.

export function isPersonalizedWelcomeMessageEnabled() {
return IS_PERSONALIZED_WELCOME_MESSAGE_ENABLED;
}

That's it for the feature toggle. Pretty simple, right?

Step 2: Reference your toggle in functional code

Here is a simple component to display the welcome message to the user.

export default function WelcomeMessage() {
return <h1>Welcome to Dashboi</h1>;
}

We want to display the user's name if the feature is turned on. To do so, we can use the function we created previously.

export default function WelcomeMessage(props) {
if (isPersonalizedWelcomeMessageEnabled()) {
return <h1>Welcome to Dashboi, {props.userFullName}</h1>;
} else {
return <h1>Welcome to Dashboi</h1>;
}
}

That was easy. Don't be too critical of the above code. It was purposefully simplified to make the concept easy to understand. In a production app, you'd want to be sure to remove code duplication wherever possible.

Now, you can simply flip the IS_PERSONALIZED_WELCOME_MESSAGE_ENABLED constant to true to enable the feature for users. Releasing a new feature is as simple as merging a pull request that makes this change, but only if you have already ensured that the feature is well tested.

Step 3: Write Tests for both implementations

You want to make sure your code is tested before you turn it on for users. Building your toggle framework as we have here makes this easy to do. To write tests, I use jest and react testing library, so this might look a little different if you are using another testing library, but the concepts should be the exact same.

At the top of your file, you'll want to import your feature toggles.

import * as FeatureToggles from "./featureToggles";

Importing in this way will allow you to set an override for any toggles you want to in the file, if you have multiple. First, we can write a test for the released version of the code:

test("the welcome message is displayed to the user without their username", () => {
const welcomeMessageEnabledMock = jest.spyOn(
FeatureToggles,
"isPersonalizedWelcomeMessageEnabled"
);
welcomeMessageEnabledMock.mockReturnValue(false);
const { queryByText } = render(<WelcomeMessage />);
expect(queryByText("Welcome To Dashboi")).toBeInTheDocument();
welcomeMessageEnabledMock.mockRestore();
});

jest.spyOn() enables you to mock only the function you specify from the file. Mocking in this way ensures that any other feature toggles in the file still function as defined.

mockRestore() resets the value of the function to its original return value.

Now we can write the test for the logic that contains the new code.

test("the welcome message is displayed to the user with their username", () => {
const welcomeMessageEnabledMock = jest.spyOn(
FeatureToggles,
"isPersonalizedWelcomeMessageEnabled"
);
welcomeMessageEnabledMock.mockReturnValue(true);
const { queryByText } = render(
<WelcomeMessage userFullName="Kayla Winslow" />
);
expect(queryByText("Welcome To Dashboi, Kayla Winslow")).toBeInTheDocument();
welcomeMessageEnabledMock.mockRestore();
});

Now, both paths in the branching logic are well tested and are feature toggle value agnostic, meaning that when you submit the pull request to turn the feature on, you don't need to change any tests. Neat!

Removing the feature toggle

After you release your feature, you may want to leave the feature toggle in the code for a while to ensure that the feature is behaving as expected. This allows you to easily turn the feature off if users encounter any issues.

Eventually, you'll want to remove the feature toggle completely from the codebase because you don't want your code littered with feature toggles forever. Failure to do so will increase the amount of technical debt that you accrue over time, which decreases the speed at which new features can be developed. So after you have validated that the feature is working as expected, you can remove the feature toggle and the getter method, refactor the code to no longer have branching logic, remove the test for the old implementation, and update the new test to not mock the call to the feature toggle getter.

Final Thoughts

Most likely, your feature will be more complex than this simple example, and you may be confused about where to use your feature toggle in your code. My philosophy is to use feature toggles at user entry points, meaning that I hide features to the end user, but release the back end portion of the code if possible. Using your feature toggles in this way can ensure that you are maintaining backwards compatibility on your back end and that you are decoupling the front end and back end effectively.

Sometimes, this isn't possible, and if you find that you need to have branching logic in your back end, you may want to consider defining your feature toggles on the back end and serving them to the front end. Doing so will mean you have a unified feature toggle framework that is easy for developers to understand.

At the end of the day, using feature toggles will help you continue to release incrementally, while securing the features that are visible to the end user. Developing a feature toggle framework is essential for any application that is released to end users.

Thanks for reading!


Originally published on Medium