Skip to main content
Skip table of contents

Flipper Usage In Specs

Last Updated:

Platform is enforcing Project/ForbidFlipperToggleInSpecs to stop new Flipper.enable and Flipper.disable usage in Vets-API specs.

Decision

Specs should not call:

  • Flipper.enable(...)

  • Flipper.disable(...)

Specs should instead stub Flipper.enabled? with the same arguments the code under test uses.

Scope

Existing specs that use Flipper.enable to control flags for logic not related to flipper.

Out of Scope: Specs whose subject is the toggle operation or side effects of it, since stubbing would break the test.

Why?

Direct mutation of global Flipper state makes tests harder to reason about and can contribute to order-dependent behavior.

In vets-api, Flipper initialization also performs environment-specific setup during boot (see config/initializers/flipper.rb), which can make direct global mutation less predictable than local stubs.

Recommended patterns

Prefer stubbing Flipper.enabled? in each test context.

Single flag example

RUBY
allow(Flipper).to receive(:enabled?).with(:my_feature).and_return(true)

Flag with actor/user example

RUBY
allow(Flipper).to receive(:enabled?).with(:my_feature, user).and_return(true)

Multiple flag example

RUBY
before do
  allow(Flipper).to receive(:enabled?).with(:feature_a).and_return(true)
  allow(Flipper).to receive(:enabled?).with(:feature_b).and_return(false)
end

Multiple flags with fall-through to real behavior

If other features may be called in the same test, let unspecified flags fall through:

RUBY
before do
  allow(Flipper).to receive(:enabled?).and_call_original

  allow(Flipper).to receive(:enabled?).with(:feature_name).and_return(true)
end

Optional helper pattern

If you want to handle multiple values more directly than having multiple statements, or if you feel stubbing is getting repetitive in your tests, here’s an example of how you can create a helper.

RUBY
def stub_flipper_flags(flag_values, call_original: false)
  # Without call_original: true, flags not listed in flag_values hit the real adapter.
  allow(Flipper).to receive(:enabled?).and_call_original if call_original

  flag_values.each do |flag, value|
    allow(Flipper).to receive(:enabled?).with(flag).and_return(value)
  end
end

# usage
stub_flipper_flags({ feature_a: true, feature_b: false })
stub_flipper_flags({ feature_name: true }, call_original: true)

If your code calls Flipper.enabled? with an actor argument, mirror that signature in your stubs:

RUBY
allow(Flipper).to receive(:enabled?).with(:my_feature, user).and_return(true)

Migration guidance

Rollout model: legacy callsites are currently suppressed inline so the Platform can enforce this for new code without a large one-time refactor. Done in PR #28509.

Existing violations are currently allowed with inline suppression so CI can enforce this for new code immediately:

RUBY
Flipper.enable(:legacy_flag) # rubocop:disable Project/ForbidFlipperToggleInSpecs

When touching a file, prefer replacing suppressions with enabled? stubs.

Team expectations

  1. Do not add new Flipper.enable/disable calls in specs.

  2. Use enabled? stubs for new or modified tests.

  3. Keep suppression comments only for legacy code you are not refactoring yet.

Rollout plan

  1. Enforce Project/ForbidFlipperToggleInSpecs in CI.

  2. Keep existing inline suppressions for legacy callsites.

  3. Require new or modified specs to use enabled? stubs.

  4. Gradually remove suppressions as files are updated.

FAQ

Is this a behavior change to production feature flags?

No. This is test-only guidance and lint enforcement.

Can I disable this Rubocop in new code?

Please, no. New tests should use stubs. If you must add a suppression, explain why in the PR description.

Does Flipper use an in-memory adapter in test?

Flipper may default to memory in Rails test setups, but in vets-api we still standardize on stubs in specs to avoid direct global mutation and to keep test setup explicit and local to the example.

What if I need to control many flags?

Use one enabled? stub per flag, or a local helper that sets all needed stubs in one place.

What if I only care about one flag and want everything else untouched?

Use and_call_original first, then override only the feature you need:

RUBY
before do
  allow(Flipper).to receive(:enabled?).and_call_original
  allow(Flipper).to receive(:enabled?).with(:feature_name).and_return(true)
end

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.