Feature flags solve a lot of deployment problems. They also create a whole new set of testing problems. Every flag introduces a branch in your code. Every branch needs to be tested. The math gets uncomfortable fast.
The State Explosion Problem
One feature flag means two states: On. Off.
Two flags means four states. Ten flags means 1,024 possible combinations.
You are not going to test all of them. Nobody is. But some of those combinations will behave unexpectedly, and your users will find them at the worst possible time. The practical approach is to identify which flag combinations actually matter for your application and test those specifically.
Flags that affect the same feature or code path are more likely to interact badly. Flags for completely independent features probably don't need to be tested together.
Test Every State of Every Flag
At a minimum, every flag should be tested in both its enabled and disabled states. This sounds obvious. It's also incredibly easy to forget. When adding a flag, developers tend to test the shiny new code path and forget to verify that the old path still works. Don't. Write explicit tests for both states:
[Fact]
public async Task Index_ReturnsNewDashboard_WhenFlagEnabled()
{
// Arrange
_featureManager
.Setup(fm => fm.IsEnabledAsync("NewDashboard"))
.ReturnsAsync(true);
// Act
var result = await _controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Equal("NewDashboard", viewResult.ViewName);
}
[Fact]
public async Task Index_ReturnsOldDashboard_WhenFlagDisabled()
{
// Arrange
_featureManager
.Setup(fm => fm.IsEnabledAsync("NewDashboard"))
.ReturnsAsync(false);
// Act
var result = await _controller.Index();
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
Assert.Equal("OldDashboard", viewResult.ViewName);
}
Both tests matter. Don't skip the disabled case.
That's usually the code path everyone assumes will keep working forever.
Test the Flag Infrastructure
Here's another fun question: What happens when your flag service is unavailable?
Nobody's perfect. Your application should have a defined behavior when the flag infrastructure goes down. A safe default is usually to treat the flag as disabled. And yes, that behavior should be tested. "Works on my machine when the flag server is up" is not a test strategy.
Write tests that simulate a flag service outage and verify that your application degrades gracefully. Users shouldn't see an error because a configuration service had a brief hiccup. The important part is that you know what happens and you've tested it.
Automate Everything
Manual testing of feature flag states doesn't scale. Have ten active flags? Asking QA to manually verify both states of every flag before every release is a fantastic way to produce burnout, missed coverage, and possibly a strongly worded email.
Automate it. Your CI pipeline should run your flag-state tests automatically. Use mocks or test configuration to control flag values in your test suite.
Don't make your CI pipeline depend on a live flag server.
Your tests should be deterministic. Your production infrastructure can provide enough chaos on its own.
Document Expected Behavior
For every flag, document what the application should do when the flag is:
- Enabled
- Disabled
That documentation becomes the specification your tests enforce. Without documentation, "works on my machine" becomes the de facto specification. With documentation, everyone has a shared understanding of what correct behavior actually means. That makes it easier to:
- Write tests
- Review code
- Diagnose bugs
- Understand old flags
- Remove flags safely
It also makes explaining expected behavior to the person filing the bug report considerably easier.
The Point
Feature flags give you control. Testing makes sure that control doesn't turn into chaos. For every flag:
- Test the enabled state.
- Test the disabled state.
- Test important flag combinations.
- Test what happens when the flag infrastructure fails.
- Automate the tests in CI.
- Document the expected behavior.
And when the feature is fully released?
Delete the flag.
Fewer flags mean fewer states, fewer tests, and fewer opportunities for your codebase to develop a personality disorder.
If you need a simple way to manage your feature flags without building your own flag infrastructure, check out FeatureFlags.app.
Test everything. Automate all of it. Document what you expect. Try not to cry.
Top comments (0)