Rulestead's supported test story in v0.1.0 is Fake-first. The public helper surface is Rulestead.TestHelpers, backed by Rulestead.Fake, so host apps can test flag behavior without Postgres, runtime refresh processes, or admin UI bootstrapping.

Use the Fake-backed path for merge-blocking tests. Save real-store coverage for integration smoke, install verification, or your own app-specific confidence checks.

Add the helper import

defmodule MyApp.CheckoutTest do
  use ExUnit.Case, async: true

  import Rulestead.TestHelpers
end

The shipped helper surface includes:

  • with_flag/3
  • put_flag/3
  • clear_flags/0
  • seed_bucket/3
  • assert_flag_evaluated/2

Scope a forced value to one block

Use with_flag/3 when you want a temporary override that restores prior fake state automatically:

test "renders the redesign when the flag is enabled" do
  with_flag "checkout-redesign", true do
    assert {:ok, html} = MyApp.Checkout.render(%{targeting_key: "user-123"})
    assert html =~ "New checkout"
  end
end

This is the safest default for isolated behavior tests because it snapshots the Fake state before the block and restores it afterward.

Seed fake state for the current test

Use put_flag/3 when you want a seeded flag to remain available for the rest of the test:

setup do
  clear_flags()
  :ok
end

test "returns the forced value from the fake-backed contract" do
  put_flag("beta-banner", false, environment: "test")

  assert {:ok, false} =
           Rulestead.Runtime.enabled?(
             "test",
             "beta-banner",
             %{targeting_key: "user-123", environment: "test"}
           )
end

clear_flags/0 resets the in-memory Fake so adjacent tests do not leak authored state into each other.

Pin deterministic bucket outcomes

Use seed_bucket/3 when you want one targeting key to land on a specific variant through the same public contract your app uses:

test "pins the blue variant for a specific user" do
  seed_bucket("checkout-color", "user-123", "blue")

  assert {:ok, "blue"} =
           Rulestead.Runtime.get_variant(
             "test",
             "checkout-color",
             %{targeting_key: "user-123", environment: "test"}
           )
end

This is the supported way to make multivariate tests deterministic. Do not reach into hash or bucket internals from host-app tests.

Assert that code actually evaluated a flag

Use assert_flag_evaluated/2 when you need to prove a code path performed an evaluation instead of accidentally bypassing it:

test "checkout code emits the eval stop event" do
  put_flag("checkout-redesign", true)

  assert_flag_evaluated "checkout-redesign" do
    MyApp.Checkout.render!(%{targeting_key: "user-123"})
  end
end

The helper asserts against bounded telemetry metadata only. It does not expose raw attributes or resolved values.

Keep host-app tests on the public surface

Prefer these layers in application tests:

  1. Rulestead.TestHelpers to seed state.
  2. the keyed runtime evaluation surface for app code.
  3. Rulestead, Rulestead.Phoenix, Rulestead.LiveView, and Rulestead.Oban where your integration actually uses those seams.

Avoid depending on:

  • Rulestead.Fake.Control directly from app tests
  • runtime cache internals
  • installer fixture helpers from this repo

Those are library-maintenance details, not the supported published-package test contract.

Published-package smoke stays aligned with this story

The release proof for v0.1.0 mirrors the same approach. The published package must be usable in a fresh consumer app where tests seed flags through the Fake-backed helper surface and install smoke validates the generated Phoenix wiring separately.

That is why this guide stays aligned with rulestead/test/rulestead/integration/install_smoke_test.exs instead of teaching path-dependency-only shortcuts.

Lifecycle Release Surface

Lifecycle verification should stay on stable public seams.

Use this layered release-facing path:

  1. docs and README content checks for lifecycle discoverability
  2. mix rulestead.lifecycle contract tests for read-only text and JSON output
  3. release_contract_test.exs for shared docs and sibling-package posture
  4. one mounted host-seam check through admin_mount_test.exs

That is the supported lifecycle verification recipe. It proves the public seam without turning internal LiveView structure into contract.

Public Seam, Not Browser-Heavy Lock-In

Prefer these checks:

  • rulestead_lifecycle_test.exs for CLI/report vocabulary and schema
  • release_contract_test.exs for lifecycle guide discoverability
  • admin_mount_test.exs for mount, route, query, and environment behavior

Avoid browser-heavy lifecycle assertions that freeze private DOM or CSS structure. The mounted host seam is public; internal selectors are not.

When to use the real store

Use the Ecto-backed store only when you are intentionally testing your own host-app integration with migrations, runtime refresh supervision, PubSub delivery, or operator workflows.

Those are integration concerns. They should not replace the Fake-backed contract as your default application test surface.